diff --git a/console/index.html b/console/index.html index 0802a80..c01a320 100644 --- a/console/index.html +++ b/console/index.html @@ -253,6 +253,7 @@ + @@ -316,6 +317,14 @@

+ diff --git a/console/src/main.ts b/console/src/main.ts index 7c108cc..f866c93 100644 --- a/console/src/main.ts +++ b/console/src/main.ts @@ -215,6 +215,80 @@ const mcp = mcpEl ? createPane(mcpEl, () => flag("mcpio")) : null; }); })(); +// "Agent configs" Debug drawer tab (studio#128) — reads purely from the +// local Config folder set above, no S3 (Brett: "forget about s3 now"). +// Whatever an admin agent (or the New Fleet wizard) writes into that same +// folder shows up here on the next Refresh / tab switch, no separate sync +// mechanism needed — it's just reading a directory. +(function setupAgentConfigsTab(): void { + const selectEl = document.getElementById("agent-configs-select") as HTMLSelectElement | null; + const refreshBtn = document.getElementById("agent-configs-refresh"); + const statusEl = document.getElementById("agent-configs-status"); + const contentEl = document.getElementById("agent-configs-content"); + const tabBtn = document.querySelector('[data-target="debug-agent-configs"]'); + if (!selectEl || !refreshBtn || !statusEl || !contentEl) return; + const FOLDER_KEY = "oab-studio.configFolder"; + + const currentFolder = (): string | null => { + try { + return localStorage.getItem(FOLDER_KEY); + } catch { + return null; + } + }; + + const showContent = async (agent: string, folder: string): Promise => { + const invoke = tauriInvoke(); + if (!invoke) return; + try { + const text = await invoke("read_local_agent_config", { folder, agent }); + contentEl.textContent = text; + statusEl.textContent = ""; + } catch (e) { + contentEl.textContent = ""; + statusEl.textContent = `read failed: ${errText(e)}`; + } + }; + + const refresh = async (): Promise => { + const invoke = tauriInvoke(); + const folder = currentFolder(); + contentEl.textContent = ""; + selectEl.innerHTML = ""; + if (!invoke) return; + if (!folder) { + statusEl.textContent = "set a Config folder first (Config tab)"; + return; + } + try { + const agents = await invoke("list_local_agent_configs", { folder }); + if (agents.length === 0) { + statusEl.textContent = "no agent configs found in this folder yet"; + return; + } + // DOM construction, not innerHTML — agent names come from local + // directory listings, not worth trusting as HTML. + for (const agent of agents) { + const opt = document.createElement("option"); + opt.value = agent; + opt.textContent = agent; + selectEl.appendChild(opt); + } + statusEl.textContent = ""; + void showContent(selectEl.value, folder); + } catch (e) { + statusEl.textContent = `list failed: ${errText(e)}`; + } + }; + + refreshBtn.addEventListener("click", () => void refresh()); + selectEl.addEventListener("change", () => { + const folder = currentFolder(); + if (folder) void showContent(selectEl.value, folder); + }); + tabBtn?.addEventListener("click", () => void refresh()); +})(); + // Build stamp (injected by vite) — shown under the brand and logged on launch, // so it's obvious which commit this build is. const BUILD = `v${__APP_VERSION__} · ${__BUILD_SHA__}`; diff --git a/console/src/styles.css b/console/src/styles.css index 0af8a95..9d89a8d 100644 --- a/console/src/styles.css +++ b/console/src/styles.css @@ -1189,6 +1189,37 @@ button.act:disabled { padding: 16px; overflow: auto; } +/* Agent configs tab (studio#128) — local-folder-only view of a deployed + agent's config.toml (mirrors the log panes' monospace/scroll shape). */ +.pane.agent-configs { + padding: 16px; + overflow: auto; + display: flex; + flex-direction: column; + gap: 10px; +} +.agent-configs-row { + display: flex; + gap: 12px; + align-items: center; +} +.agent-configs-row .compose-select { + flex: 1; +} +.agent-configs-content { + margin: 0; + padding: 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--text); + font-family: ui-monospace, monospace; + font-size: 12px; + white-space: pre-wrap; + word-break: break-word; + overflow: auto; + min-height: 120px; +} .config-form { display: flex; flex-direction: column; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c5ef653..6e551da 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -441,6 +441,39 @@ async fn resolve_vendor_image_tags(core: tauri::State<'_, Core>, vendor: String) } } +/// Lists agent names under the local "Config folder" (studio#128) that +/// have a `config.toml` — backs the Debug drawer's "Agent configs" tab. +/// Pure local filesystem, no sidecar/AWS/k8s involved at all: the local +/// folder is Studio's own mirror the New Fleet wizard writes alongside its +/// S3 upload, not a read-through to the S3 source of truth (Brett: +/// "forget about s3 now" for this feature — see the Config folder +/// setting's own doc comment for the full reasoning). +#[tauri::command] +fn list_local_agent_configs(folder: String) -> Result, String> { + let entries = std::fs::read_dir(&folder).map_err(|e| format!("read {folder}: {e}"))?; + let mut names = Vec::new(); + for entry in entries { + let entry = entry.map_err(|e| e.to_string())?; + let path = entry.path(); + if path.is_dir() && path.join("config.toml").is_file() { + if let Some(name) = entry.file_name().to_str() { + names.push(name.to_string()); + } + } + } + names.sort(); + Ok(names) +} + +/// Reads one agent's `config.toml` from the local "Config folder" — +/// `//config.toml`, the same layout +/// `list_local_agent_configs` scans. +#[tauri::command] +fn read_local_agent_config(folder: String, agent: String) -> Result { + let path = std::path::Path::new(&folder).join(&agent).join("config.toml"); + std::fs::read_to_string(&path).map_err(|e| format!("read {}: {e}", path.display())) +} + /// Bridge command: namespaces in a kubeconfig context (studio#104), via the /// sidecar's `list_namespaces` tool — backs the New Fleet wizard's namespace /// field's autocomplete. @@ -830,6 +863,8 @@ pub fn run() { list_aws_profiles, list_k8s_contexts, resolve_vendor_image_tags, + list_local_agent_configs, + read_local_agent_config, list_namespaces, list_service_accounts, k8s_fleet_config,