agent-rooms 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,357 +0,0 @@
1
- export function dashboardHtml() {
2
- return String.raw `<!doctype html>
3
- <html lang="en">
4
- <head>
5
- <meta charset="utf-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- <title>Agent Rooms Control</title>
8
- <style>
9
- :root {
10
- color-scheme: dark;
11
- --bg: #05070F;
12
- --bg2: #0B1020;
13
- --panel: rgba(255,255,255,.022);
14
- --ink: #EAEFF8;
15
- --muted: #8A93A6;
16
- --faint: #5E6678;
17
- --line: rgba(255,255,255,.07);
18
- --line2: rgba(255,255,255,.12);
19
- --cyan: #38BDF8;
20
- --cyan2: #7DD3FC;
21
- --purple: #A78BFA;
22
- --green: #22C55E;
23
- --amber: #FBBF24;
24
- --red: #F87171;
25
- --gold: #FACC15;
26
- --blue: #38BDF8;
27
- --shadow: 0 24px 60px -28px rgba(0,0,0,.85);
28
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
29
- }
30
- * { box-sizing: border-box; }
31
- body {
32
- margin: 0; color: var(--ink);
33
- background:
34
- radial-gradient(1100px 620px at 78% -8%, rgba(56,189,248,.10), transparent 60%),
35
- radial-gradient(900px 560px at -6% 8%, rgba(167,139,250,.09), transparent 55%),
36
- var(--bg);
37
- -webkit-font-smoothing: antialiased;
38
- }
39
- .mono, pre, .event, input, select { font-family: ui-monospace, "SF Mono", SFMono-Regular, Consolas, "Liberation Mono", monospace; }
40
- button, input, select { font: inherit; }
41
- ::selection { background: rgba(56,189,248,.28); }
42
- ::-webkit-scrollbar { width: 9px; height: 9px; }
43
- ::-webkit-scrollbar-thumb { background: rgba(255,255,255,.1); border-radius: 99px; }
44
- .shell { min-height: 100vh; display: grid; grid-template-columns: 248px 1fr; }
45
- aside { border-right: 1px solid var(--line); padding: 22px 16px; background: linear-gradient(180deg, rgba(11,16,32,.55), rgba(5,7,15,.2)); position: sticky; top: 0; height: 100vh; }
46
- main { padding: 28px 30px; max-width: 1200px; width: 100%; }
47
- .brand { display: flex; align-items: center; gap: 9px; font-size: 14.5px; font-weight: 700; letter-spacing: -.01em; margin-bottom: 5px; color: #F8FAFC; }
48
- .brand::before { content: ""; width: 19px; height: 19px; border-radius: 6px; flex: none; background: linear-gradient(140deg, var(--cyan), var(--purple)); box-shadow: 0 0 16px -3px rgba(56,189,248,.7); }
49
- .sub { color: var(--faint); font-size: 12px; line-height: 1.5; margin-bottom: 22px; }
50
- .nav { display: grid; gap: 3px; }
51
- .nav button { display: flex; align-items: center; gap: 9px; border: 0; background: transparent; text-align: left; padding: 9px 11px; border-radius: 9px; color: var(--muted); cursor: pointer; font-size: 13.5px; transition: background .12s, color .12s; }
52
- .nav button:hover { background: rgba(255,255,255,.04); color: var(--ink); }
53
- .nav button.active { background: linear-gradient(90deg, rgba(56,189,248,.14), rgba(56,189,248,.04)); color: #F8FAFC; font-weight: 600; box-shadow: inset 2px 0 0 var(--cyan); }
54
- .top { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 20px; }
55
- h1 { font-size: 23px; margin: 0 0 5px; letter-spacing: -.02em; font-weight: 650; }
56
- .meta { color: var(--faint); font-size: 12px; }
57
- .banner { background: var(--panel); border: 1px solid var(--line2); backdrop-filter: blur(10px); border-radius: 13px; padding: 15px 17px; display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 18px; position: relative; overflow: hidden; }
58
- .banner::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--cyan); }
59
- .banner.ready::before { background: var(--green); }
60
- .banner strong { display: block; margin-bottom: 3px; font-weight: 600; color: #F8FAFC; }
61
- .grid { display: grid; gap: 16px; }
62
- .two { grid-template-columns: minmax(0, 1.25fr) minmax(330px, .75fr); align-items: start; }
63
- .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow); overflow: hidden; }
64
- .panel h2 { font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; color: var(--faint); padding: 15px 16px 9px; margin: 0; font-weight: 600; }
65
- .step { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; gap: 13px; padding: 14px 16px; border-top: 1px solid var(--line); align-items: center; }
66
- .step:first-of-type { border-top: 0; }
67
- .step.done { opacity: .62; padding-top: 11px; padding-bottom: 11px; }
68
- .step.frontier { background: linear-gradient(90deg, rgba(56,189,248,.07), transparent); box-shadow: inset 2px 0 0 var(--cyan); }
69
- .dot { width: 11px; height: 11px; border-radius: 50%; margin-top: 3px; background: var(--faint); box-shadow: 0 0 0 4px rgba(255,255,255,.03); }
70
- .ready .dot { background: var(--green); box-shadow: 0 0 10px -1px var(--green); }
71
- .action_needed .dot { background: var(--amber); box-shadow: 0 0 10px -1px var(--amber); }
72
- .blocked .dot { background: var(--red); box-shadow: 0 0 10px -1px var(--red); }
73
- .working .dot { background: var(--cyan); animation: pulse 1.2s infinite; box-shadow: 0 0 10px -1px var(--cyan); }
74
- .attention .dot { background: var(--gold); box-shadow: 0 0 10px -1px var(--gold); }
75
- .absent .dot { background: var(--faint); }
76
- @keyframes pulse { 0%,100% { opacity: .4; } 50% { opacity: 1; } }
77
- .label { font-weight: 600; margin-bottom: 3px; color: #EAEFF8; font-size: 13.5px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
78
- .hint { color: var(--muted); font-size: 12.5px; line-height: 1.5; }
79
- .btn { border: 1px solid var(--cyan); background: var(--cyan); color: #05070F; border-radius: 9px; min-height: 35px; padding: 0 14px; cursor: pointer; white-space: nowrap; font-weight: 600; font-size: 12.5px; transition: filter .12s, transform .04s; box-shadow: 0 0 20px -8px rgba(56,189,248,.9); }
80
- .btn:hover { filter: brightness(1.08); }
81
- .btn:active { transform: translateY(1px); }
82
- .btn.secondary { background: rgba(255,255,255,.04); color: var(--ink); border-color: var(--line2); box-shadow: none; }
83
- .btn.secondary:hover { background: rgba(255,255,255,.08); }
84
- .btn.gold { background: var(--gold); border-color: var(--gold); color: #1A1404; box-shadow: 0 0 20px -8px rgba(250,204,21,.9); }
85
- .btn:disabled { opacity: .4; cursor: not-allowed; box-shadow: none; filter: none; }
86
- .kv { display: grid; gap: 9px; padding: 13px 16px; }
87
- .row { display: flex; align-items: center; justify-content: space-between; gap: 12px; border-top: 1px solid var(--line); padding: 12px 16px; font-size: 13px; }
88
- .row:first-of-type { border-top: 0; }
89
- .pill { border: 1px solid var(--line2); border-radius: 999px; padding: 3px 9px; font-size: 10.5px; letter-spacing: .04em; color: var(--muted); white-space: nowrap; font-weight: 600; }
90
- .pill.ready { color: var(--green); border-color: rgba(34,197,94,.3); background: rgba(34,197,94,.1); }
91
- .pill.warn { color: var(--amber); border-color: rgba(251,191,36,.3); background: rgba(251,191,36,.1); }
92
- .pill.bad { color: var(--red); border-color: rgba(248,113,113,.3); background: rgba(248,113,113,.1); }
93
- .activity { max-height: 520px; overflow: auto; padding: 4px 0; }
94
- .event { padding: 9px 16px; border-top: 1px solid var(--line); font-size: 11.5px; color: #B6C0D4; line-height: 1.5; }
95
- .event:first-child { border-top: 0; }
96
- .form { display: grid; gap: 12px; padding: 16px; }
97
- label { display: grid; gap: 6px; font-size: 12px; color: var(--muted); }
98
- input, select { width: 100%; border: 1px solid var(--line2); border-radius: 9px; padding: 10px 12px; background: rgba(5,7,15,.6); color: var(--ink); font-size: 12.5px; }
99
- input:focus, select:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 0 3px rgba(56,189,248,.14); }
100
- input::placeholder { color: var(--faint); }
101
- option { background: #0B1020; }
102
- pre { margin: 0; padding: 14px 16px; overflow: auto; border-top: 1px solid var(--line); font-size: 11.5px; background: rgba(5,7,15,.5); color: #9FB0C8; line-height: 1.55; }
103
- .hidden { display: none !important; }
104
- .toast { position: fixed; right: 20px; bottom: 20px; width: min(560px, calc(100vw - 40px)); background: rgba(11,16,32,.96); backdrop-filter: blur(14px); color: #EAEFF8; border: 1px solid var(--line2); border-radius: 12px; padding: 13px 15px; box-shadow: var(--shadow); font-size: 12.5px; white-space: pre-wrap; line-height: 1.5; }
105
- @media (max-width: 850px) {
106
- .shell { grid-template-columns: 1fr; }
107
- aside { border-right: 0; border-bottom: 1px solid var(--line); position: static; height: auto; }
108
- main { padding: 18px; }
109
- .two { grid-template-columns: 1fr; }
110
- .top, .banner, .row { flex-direction: column; align-items: stretch; }
111
- .step { grid-template-columns: 18px minmax(0, 1fr); }
112
- .step .btn { grid-column: 2; justify-self: start; }
113
- }
114
- </style>
115
- </head>
116
- <body>
117
- <div id="app"></div>
118
- <script>
119
- const token = new URLSearchParams(location.search).get("token") || "";
120
- let snapshot = null;
121
- let active = "home";
122
- let autoFocused = false;
123
- let userSelectedTab = false;
124
- let toastTimer = null;
125
- const app = document.getElementById("app");
126
-
127
- function api(path, options = {}) {
128
- return fetch(path, {
129
- ...options,
130
- headers: { "content-type": "application/json", "x-agent-rooms-token": token, ...(options.headers || {}) }
131
- }).then(async (res) => {
132
- const text = await res.text();
133
- const body = text ? JSON.parse(text) : {};
134
- if (!res.ok) throw new Error(body.error || res.statusText);
135
- return body;
136
- });
137
- }
138
-
139
- async function load() {
140
- snapshot = await api("/api/readiness");
141
- if (!autoFocused && !userSelectedTab && snapshot.readiness && !snapshot.readiness.nextAction && snapshot.readiness.listener.running) {
142
- active = "activity";
143
- autoFocused = true;
144
- }
145
- render();
146
- }
147
-
148
- async function act(action, payload = {}) {
149
- if (action.id === "config.binding.focus") {
150
- active = "agents";
151
- render();
152
- return;
153
- }
154
- if (action.id === "host.token.focus") {
155
- active = "advanced";
156
- render();
157
- return;
158
- }
159
- if (action.manual) {
160
- showToast(action.command || "Manual step required.");
161
- return;
162
- }
163
- try {
164
- const result = await api("/api/actions", { method: "POST", body: JSON.stringify({ action: action.id, host: action.host, payload: { ...(action.payload || {}), ...payload } }) });
165
- snapshot = result.snapshot;
166
- if (result.command) showToast(result.command);
167
- else if (result.message) showToast(result.message);
168
- render();
169
- } catch (err) {
170
- showToast("Action failed: " + err.message);
171
- }
172
- }
173
-
174
- // No-code pairing: once pairing has started (the approval page is open in the
175
- // browser), poll quietly until the device is approved — the human just clicks
176
- // Approve over there, never copies a code, never clicks "Check approval" here.
177
- let pairPoller = null;
178
- async function pollPairSilently() {
179
- try {
180
- const result = await api("/api/actions", { method: "POST", body: JSON.stringify({ action: "pair.poll" }) });
181
- snapshot = result.snapshot;
182
- if (!snapshot.pendingPair) showToast("Device paired. Connecting your listener…");
183
- render();
184
- } catch (err) { /* keep polling; transient */ }
185
- }
186
- function ensurePairPolling() {
187
- const pending = snapshot && snapshot.pendingPair;
188
- if (pending && !pairPoller) pairPoller = setInterval(pollPairSilently, 3000);
189
- else if (!pending && pairPoller) { clearInterval(pairPoller); pairPoller = null; }
190
- }
191
-
192
- function statusText(status) {
193
- return {
194
- ready: "READY",
195
- action_needed: "ACTION NEEDED",
196
- blocked: "BLOCKED",
197
- working: "WORKING",
198
- attention: "ATTENTION",
199
- absent: "NOT AVAILABLE"
200
- }[status] || status;
201
- }
202
-
203
- function render() {
204
- if (!token) {
205
- app.innerHTML = "<main><h1>Blocked</h1><p>Missing dashboard token.</p></main>";
206
- return;
207
- }
208
- const r = snapshot && snapshot.readiness;
209
- if (!r) {
210
- app.innerHTML = "<main><h1>Agent Rooms Control</h1><p>Loading local state...</p></main>";
211
- return;
212
- }
213
- app.innerHTML = '<div class="shell"><aside>' + nav() + '</aside><main>' + page(r) + '</main></div>' + '<div id="toast" class="toast hidden"></div>';
214
- bind();
215
- ensurePairPolling();
216
- }
217
-
218
- function nav() {
219
- return '<div class="brand">Agent Rooms Control</div><div class="sub">Local dashboard on 127.0.0.1. Buttons are driven by detected machine state.</div><div class="nav">' +
220
- ["home:Readiness", "agents:Agents", "activity:Activity", "advanced:Advanced"].map((item) => {
221
- const [id, label] = item.split(":");
222
- return '<button data-tab="' + id + '" class="' + (active === id ? "active" : "") + '">' + label + '</button>';
223
- }).join("") + '</div>';
224
- }
225
-
226
- function page(r) {
227
- if (active === "agents") return agentsPage(r);
228
- if (active === "activity") return activityPage();
229
- if (active === "advanced") return advancedPage(r);
230
- return homePage(r);
231
- }
232
-
233
- function header(title, r) {
234
- return '<div class="top"><div><h1>' + title + '</h1><div class="meta">API ' + esc(r.apiBase) + ' - config ' + esc(r.configPath) + '</div></div><button class="btn secondary" data-refresh>Re-detect</button></div>';
235
- }
236
-
237
- function homePage(r) {
238
- const next = r.nextAction;
239
- const pair = snapshot.pendingPair
240
- ? '<div class="banner"><div><strong>Waiting for approval — code ' + esc(snapshot.pendingPair.user_code) + '</strong><div class="meta">We opened the approval page in your browser. Click <b>Approve &amp; pair</b> there (you\'re already signed in) — this pairs automatically, no code to copy.</div></div><button class="btn secondary" data-action="' + encodeURIComponent(JSON.stringify({ id: "pair.poll", label: "Check now" })) + '">Check now</button></div>'
241
- : "";
242
- const banner = next
243
- ? '<div class="banner"><div><strong>Next: ' + esc(next.label) + '</strong><div class="meta">' + esc(next.hint) + '</div></div>' + actionButton(next.action) + '</div>'
244
- : '<div class="banner ready"><div><strong>All required steps are ready.</strong><div class="meta">Open Activity to watch wakes and errors.</div></div></div>';
245
- return header("Readiness", r) + pair + banner + '<div class="grid two"><section class="panel"><h2>Path</h2>' + r.steps.map(stepHtml).join("") + '</section>' + sideStatus(r) + '</div>';
246
- }
247
-
248
- function stepHtml(step) {
249
- const cls = "step " + step.status + (snapshot.readiness.nextAction && snapshot.readiness.nextAction.id === step.id ? " frontier" : "") + (step.status === "ready" ? " done" : "");
250
- return '<div class="' + cls + '"><div class="dot"></div><div><div class="label">' + esc(step.label) + ' <span class="pill">' + statusText(step.status) + '</span></div><div class="hint">' + esc(step.hint) + '</div></div>' + actionButton(step.action) + '</div>';
251
- }
252
-
253
- function sideStatus(r) {
254
- return '<section class="panel"><h2>Local Truth</h2>' +
255
- '<div class="row"><span>Device</span><span class="pill ' + (r.device.paired ? "ready" : "warn") + '">' + (r.device.paired ? "PAIRED" : "NOT PAIRED") + '</span></div>' +
256
- '<div class="row"><span>Listener<br><span class="meta">' + (r.listener.running ? 'Serving ' + esc(r.listener.servingCount) + ' instance(s)' : 'Stopped') + '</span></span><span>' + listenerControls(r) + '</span></div>' +
257
- '<div class="row"><span>Dashboard</span><span>' + (r.dashboard.updateAvailable ? actionButton({ id: "dashboard.update", label: "Update" }, "gold") + ' ' : '') + '<span class="pill ' + (r.dashboard.updateAvailable ? "warn" : "ready") + '">' + (r.dashboard.updateAvailable ? "UPDATE AVAILABLE" : "UP TO DATE") + '</span></span></div>' +
258
- Object.values(r.hosts).map((h) => '<div class="row"><span>' + esc(h.label) + '<br><span class="meta">' + esc(h.version || h.detail) + '</span></span><span class="pill ' + (!h.detected ? "bad" : h.versionOk ? "ready" : "warn") + '">' + (!h.detected ? "MISSING" : h.versionOk ? "DETECTED" : "UPDATE") + '</span></div>').join("") +
259
- '<h2>Diagnose</h2>' + r.diagnostics.map((d) => '<div class="row"><span><strong>' + esc(d.symptom) + '</strong><br><span class="meta">' + esc(d.cause) + '</span></span>' + actionButton(d.action, "secondary") + '</div>').join("") +
260
- '</section>';
261
- }
262
-
263
- function listenerControls(r) {
264
- if (r.listener.running) {
265
- return actionButton({ id: "listener.restart", label: "Restart" }, "secondary") + ' ' + actionButton({ id: "listener.stop", label: "Stop" }, "secondary");
266
- }
267
- if (r.device.paired && r.agents.some((a) => !a.paused)) {
268
- return actionButton({ id: "listener.start", label: "Start" }, "secondary");
269
- }
270
- return '<span class="pill warn">WAITING</span>';
271
- }
272
-
273
- function agentsPage(r) {
274
- return header("Agents", r) + '<div class="grid two"><section class="panel"><h2>Bindings</h2>' +
275
- (r.agents.length ? r.agents.map(agentRow).join("") : '<div class="kv meta">No bindings yet.</div>') +
276
- '</section><section class="panel"><h2>Add / Replace Binding</h2><form class="form" id="bindingForm">' +
277
- '<label>Agent plate<input name="agent" placeholder="BRNL-AGT-XXXXXXXX" required /></label>' +
278
- '<label>Room id(s)<input name="rooms" placeholder="room_1, room_2" required /></label>' +
279
- '<label>Workspace path<input name="workspace" placeholder="D:\\\\your-project" required /></label>' +
280
- '<label>Host<select name="host"><option value="claude_code">Claude Code</option><option value="codex">Codex</option><option value="gemini">Gemini CLI</option><option value="cursor">Cursor CLI</option><option value="openclaw">OpenClaw</option></select></label>' +
281
- '<button class="btn" type="submit">Save binding</button></form></section></div>';
282
- }
283
-
284
- function agentRow(a) {
285
- const status = a.paused ? "PAUSED" : a.workspaceExists ? "READY" : "PATH MISSING";
286
- const cls = a.paused ? "warn" : a.workspaceExists ? "ready" : "warn";
287
- const pauseAction = a.paused
288
- ? { id: "agent.resume", label: "Resume", payload: { agent: a.agent } }
289
- : { id: "agent.pause", label: "Pause", payload: { agent: a.agent } };
290
- const removeAction = { id: "config.binding.remove", label: "Remove", payload: { agent: a.agent, workspace: a.workspace } };
291
- const cost = a.host === "codex" ? '<br><span class="meta">Waking this agent uses your ChatGPT/Codex account usage.</span>' : "";
292
- return '<div class="row"><span><strong>' + esc(a.agent) + '</strong><br><span class="meta">' + esc(a.host) + ' - ' + esc(a.workspace) + '<br>rooms: ' + esc(a.rooms.join(", ")) + '</span>' + cost + '</span><span><span class="pill ' + cls + '">' + status + '</span><br>' + actionButton(pauseAction, "secondary") + ' ' + actionButton(removeAction, "secondary") + '</span></div>';
293
- }
294
-
295
- function activityPage() {
296
- const rows = (snapshot.activity || []).slice().reverse().map((e) => '<div class="event">[' + new Date(e.at).toLocaleTimeString() + '] ' + esc(e.level.toUpperCase()) + ' ' + esc(e.message) + '</div>').join("");
297
- return header("Activity", snapshot.readiness) + '<section class="panel"><h2>Live feed</h2><div class="activity">' + (rows || '<div class="event">No events yet.</div>') + '</div></section>';
298
- }
299
-
300
- function advancedPage(r) {
301
- return header("Advanced", r) + '<div class="grid two"><section class="panel"><h2>Commands</h2><pre>' + esc(r.advanced.commands.join("\n")) + '</pre><h2>Codex Bearer Token</h2><form class="form" id="tokenForm"><label>Agent Rooms token<input name="token" type="password" placeholder="Paste token for Codex MCP sessions" /></label><button class="btn" type="submit">Save token</button><button class="btn secondary" type="button" data-action="' + encodeURIComponent(JSON.stringify({ id: "host.clearToken", label: "Clear token", host: "codex", payload: { host: "codex" } })) + '">Clear token</button><div class="meta">Stored locally in the dashboard config; not shown back in the UI.</div></form></section><section class="panel"><h2>Raw Config</h2><pre>' + esc(JSON.stringify(r.advanced.rawConfig, null, 2)) + '</pre></section></div>';
302
- }
303
-
304
- function actionButton(action, style = "") {
305
- if (!action) return "";
306
- return '<button class="btn ' + style + '" data-action="' + encodeURIComponent(JSON.stringify(action)) + '">' + esc(action.label) + '</button>';
307
- }
308
-
309
- function bind() {
310
- document.querySelectorAll("[data-tab]").forEach((btn) => btn.addEventListener("click", () => { userSelectedTab = true; active = btn.dataset.tab; render(); }));
311
- document.querySelectorAll("[data-refresh]").forEach((btn) => btn.addEventListener("click", load));
312
- document.querySelectorAll("[data-action]").forEach((btn) => btn.addEventListener("click", () => act(JSON.parse(decodeURIComponent(btn.dataset.action)))));
313
- const form = document.getElementById("bindingForm");
314
- if (form) form.addEventListener("submit", (event) => {
315
- event.preventDefault();
316
- const data = new FormData(form);
317
- act({ id: "config.binding.save", label: "Save binding" }, {
318
- agent: String(data.get("agent") || ""),
319
- rooms: String(data.get("rooms") || ""),
320
- workspace: String(data.get("workspace") || ""),
321
- host: String(data.get("host") || "claude_code")
322
- });
323
- });
324
- const tokenForm = document.getElementById("tokenForm");
325
- if (tokenForm) tokenForm.addEventListener("submit", (event) => {
326
- event.preventDefault();
327
- const data = new FormData(tokenForm);
328
- act({ id: "host.saveToken", label: "Save token", host: "codex" }, {
329
- host: "codex",
330
- token: String(data.get("token") || "")
331
- });
332
- });
333
- }
334
-
335
- function showToast(message) {
336
- const el = document.getElementById("toast");
337
- if (!el) return;
338
- el.textContent = message;
339
- el.classList.remove("hidden");
340
- clearTimeout(toastTimer);
341
- toastTimer = setTimeout(() => el.classList.add("hidden"), 9000);
342
- }
343
-
344
- function esc(value) {
345
- return String(value ?? "").replace(/[&<>"']/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[ch]));
346
- }
347
-
348
- window.addEventListener("focus", load);
349
- setInterval(load, 10000);
350
- const events = new EventSource("/api/events?token=" + encodeURIComponent(token));
351
- events.onmessage = (event) => { snapshot = JSON.parse(event.data); render(); };
352
- load().catch((err) => { app.innerHTML = "<main><h1>Unable to load</h1><p>" + esc(err.message) + "</p></main>"; });
353
- </script>
354
- </body>
355
- </html>`;
356
- }
357
- //# sourceMappingURL=ui.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/dashboard/ui.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiWX,CAAC;AACT,CAAC"}