Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions console/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@
<button class="tab is-active" data-target="log">Activity</button>
<button class="tab" data-target="mcpio">MCP · oab-mcp</button>
<button class="tab" data-target="debug-config">Config</button>
<button class="tab" data-target="debug-agent-configs">Agent configs</button>
<span class="tabs-spacer"></span>
<button class="tab-filter" id="log-download" type="button" title="Download the active tab's log as a text file">Download</button>
<button class="tab-filter" id="log-level" type="button" title="Activity verbosity — INFO+ hides DEBUG (e.g. keepalives); click for DEBUG+">INFO+</button>
Expand Down Expand Up @@ -316,6 +317,14 @@
</p>
</div>
</div>
<div id="debug-agent-configs" class="pane agent-configs" hidden>
<div class="agent-configs-row">
<select id="agent-configs-select" class="compose-select"></select>
<button class="cfg-btn cfg-btn-ghost" id="agent-configs-refresh" type="button">Refresh</button>
</div>
<p class="config-hint" id="agent-configs-status"></p>
<pre id="agent-configs-content" class="agent-configs-content"></pre>
</div>
</aside>
</main>
<script type="module" src="/src/main.ts"></script>
Expand Down
74 changes: 74 additions & 0 deletions console/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLButtonElement>('[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<void> => {
const invoke = tauriInvoke();
if (!invoke) return;
try {
const text = await invoke<string>("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<void> => {
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<string[]>("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__}`;
Expand Down
31 changes: 31 additions & 0 deletions console/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
35 changes: 35 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<String>, 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" —
/// `<folder>/<agent>/config.toml`, the same layout
/// `list_local_agent_configs` scans.
#[tauri::command]
fn read_local_agent_config(folder: String, agent: String) -> Result<String, String> {
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.
Expand Down Expand Up @@ -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,
Expand Down
Loading