|
1 | 1 | import { defaultSource } from "./source"; |
2 | | -import { renderRoster, renderIdentity } from "./render"; |
| 2 | +import { renderRoster, renderIdentity, renderFleetConfig } from "./render"; |
| 3 | +import type { FleetConfig } from "./types"; |
3 | 4 | import { createPane, bindBackend, type Level } from "./log"; |
4 | 5 |
|
5 | 6 | const POLL_MS = 5000; |
6 | | -const CLUSTER = "oab"; |
| 7 | +const DEFAULT_CLUSTER = "oab"; |
| 8 | + |
| 9 | +// The active fleet's cluster drives every read (roster + identity) and, through |
| 10 | +// oab-mcp's per-cluster binding, which credential/account we manage as. Selecting |
| 11 | +// a fleet in the config panel is the "switch" step of the ADR #19 loop. |
| 12 | +let activeCluster = DEFAULT_CLUSTER; |
| 13 | +let fleetConfig: FleetConfig | null = null; |
7 | 14 |
|
8 | 15 | const roster = document.getElementById("roster"); |
9 | 16 | const identityEl = document.getElementById("identity"); |
| 17 | +const configEl = document.getElementById("config"); |
10 | 18 | const clusterLabel = document.getElementById("cluster-label"); |
11 | 19 | const pollStatus = document.getElementById("poll-status"); |
12 | 20 | const logEl = document.getElementById("log"); |
@@ -67,7 +75,7 @@ let lastError = ""; |
67 | 75 | async function tick(): Promise<void> { |
68 | 76 | if (!roster) return; |
69 | 77 | try { |
70 | | - const deployments = await source.listDeployments(CLUSTER); |
| 78 | + const deployments = await source.listDeployments(activeCluster); |
71 | 79 | renderRoster(roster, deployments); |
72 | 80 | if (lastError) { |
73 | 81 | note("info", `roster recovered — ${deployments.length} deployment(s)`); |
@@ -96,13 +104,48 @@ async function tick(): Promise<void> { |
96 | 104 | async function refreshIdentity(): Promise<void> { |
97 | 105 | if (!identityEl) return; |
98 | 106 | try { |
99 | | - renderIdentity(identityEl, await source.runtimeContext(CLUSTER)); |
| 107 | + renderIdentity(identityEl, await source.runtimeContext(activeCluster)); |
100 | 108 | } catch (e) { |
101 | 109 | note("error", `identity: ${errText(e)}`); |
102 | 110 | renderIdentity(identityEl, null); |
103 | 111 | } |
104 | 112 | } |
105 | 113 |
|
| 114 | +// The fleet-binding config panel (ADR #19 "declare"). Fetched once on boot; the |
| 115 | +// bindings are read at core startup, so they don't change under us at runtime. |
| 116 | +async function refreshConfig(): Promise<void> { |
| 117 | + if (!configEl) return; |
| 118 | + try { |
| 119 | + fleetConfig = await source.fleetConfig(); |
| 120 | + renderFleetConfig(configEl, fleetConfig, activeCluster); |
| 121 | + } catch (e) { |
| 122 | + note("error", `fleet config: ${errText(e)}`); |
| 123 | + fleetConfig = null; |
| 124 | + renderFleetConfig(configEl, null, activeCluster); |
| 125 | + } |
| 126 | +} |
| 127 | + |
| 128 | +// Switch the active fleet: re-point every read at its cluster (and thus its |
| 129 | +// bound credential) and refresh immediately, so "switch fleet" == "switch |
| 130 | +// managing account" the ADR calls for. No-op if it's already active. |
| 131 | +function selectCluster(cluster: string): void { |
| 132 | + if (!cluster || cluster === activeCluster) return; |
| 133 | + activeCluster = cluster; |
| 134 | + if (clusterLabel) clusterLabel.textContent = activeCluster; |
| 135 | + note("info", `switched to cluster "${activeCluster}"`); |
| 136 | + if (configEl) renderFleetConfig(configEl, fleetConfig, activeCluster); |
| 137 | + void refreshIdentity(); |
| 138 | + void tick(); |
| 139 | +} |
| 140 | + |
| 141 | +// One delegated listener: a click on any fleet button switches to its cluster. |
| 142 | +if (configEl) { |
| 143 | + configEl.addEventListener("click", (ev) => { |
| 144 | + const btn = (ev.target as HTMLElement).closest<HTMLElement>("[data-cluster]"); |
| 145 | + if (btn?.dataset.cluster) selectCluster(btn.dataset.cluster); |
| 146 | + }); |
| 147 | +} |
| 148 | + |
106 | 149 | // The Tauri command bridge — present only inside the desktop shell (the browser |
107 | 150 | // build has no `__TAURI__`, so callers no-op / hide their UI). |
108 | 151 | type Invoke = <T>(cmd: string, args?: Record<string, unknown>) => Promise<T>; |
@@ -188,10 +231,11 @@ function setupUpdater(): void { |
188 | 231 | async function boot(): Promise<void> { |
189 | 232 | note("info", `OAB Studio ${BUILD} (built ${__BUILD_TIME__})`); |
190 | 233 | if (activity && mcp) await bindBackend(activity, mcp); |
191 | | - if (clusterLabel) clusterLabel.textContent = CLUSTER; |
192 | | - note("info", `polling cluster "${CLUSTER}" every ${POLL_MS / 1000}s`); |
| 234 | + if (clusterLabel) clusterLabel.textContent = activeCluster; |
| 235 | + note("info", `polling cluster "${activeCluster}" every ${POLL_MS / 1000}s`); |
193 | 236 | setupUpdater(); |
194 | 237 | await startCore(); |
| 238 | + void refreshConfig(); |
195 | 239 | void refreshIdentity(); |
196 | 240 | void tick(); |
197 | 241 | window.setInterval(() => void tick(), POLL_MS); |
|
0 commit comments