dashboard: serve the dashboard at /, fold kept state in as a tab, add a pages menu
Drop the H0M3 tile hub and the core page. The dashboard is now the index; kept state is its fifth tab. A pages menu in the chrome links flow, stats, API docs and the swarm UI. The swarm apex URL comes from services.hyperhive.swarm.domain via HIVE_SWARM_PUBLIC_URL and the new swarm_public_url field of the state snapshot.
This commit is contained in:
parent
265f8a47cc
commit
0622736b30
35 changed files with 661 additions and 986 deletions
102
frontend/packages/dashboard/src/kept.js
Normal file
102
frontend/packages/dashboard/src/kept.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// K3PT ST4T3 tab of the dashboard: kept state from agents whose container
|
||||
// is absent (tombstones), with a purge button per row. Owns its slice of
|
||||
// state like agents.js / builds.js do; tabs.js feeds it the /api/state
|
||||
// snapshot and the `tombstones_changed` stream event.
|
||||
|
||||
import { $, form } from "./common.js";
|
||||
import { el } from "@hive/shared/dom.js";
|
||||
|
||||
let tombstonesState = [];
|
||||
|
||||
const fmtBytes = (n) => {
|
||||
if (n < 1024) return n + " B";
|
||||
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB";
|
||||
if (n < 1024 * 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + " MB";
|
||||
return (n / (1024 * 1024 * 1024)).toFixed(2) + " GB";
|
||||
};
|
||||
|
||||
const fmtAgeDays = (ts) => {
|
||||
if (!ts) return "?";
|
||||
const d = Math.floor((Date.now() / 1000 - ts) / 86400);
|
||||
if (d <= 0) return "today";
|
||||
if (d === 1) return "1 day ago";
|
||||
return d + " days ago";
|
||||
};
|
||||
|
||||
/** Number of kept-state entries, for the tab's count pill. */
|
||||
export function keptCount() {
|
||||
return tombstonesState.length;
|
||||
}
|
||||
|
||||
export function syncKeptFromSnapshot(s) {
|
||||
tombstonesState = (s.tombstones || []).slice();
|
||||
}
|
||||
|
||||
export function renderKept() {
|
||||
const root = $("tombstones-section");
|
||||
if (!root) return;
|
||||
root.replaceChildren();
|
||||
if (!tombstonesState.length) {
|
||||
root.append(el("p", { class: "empty" }, "no kept state — clean"));
|
||||
return;
|
||||
}
|
||||
// Only shown alongside actual rows — a caveat over an empty list is noise.
|
||||
// Wording is deliberately about what the list *is* rather than what it
|
||||
// isn't: nothing records a destroy, so "container absent" is the only thing
|
||||
// the backend can actually tell.
|
||||
const warn = el("hive-warn", { level: "warning" });
|
||||
warn.append(
|
||||
el("strong", {}, "shows every agent whose container is absent"),
|
||||
" — not only destroyed ones. An agent part-way through being spawned " +
|
||||
"looks identical here, because nothing records a destroy. Check it is " +
|
||||
"really gone before you PURG3.",
|
||||
);
|
||||
root.append(warn);
|
||||
const ul = el("ul", { class: "containers" });
|
||||
for (const t of tombstonesState) {
|
||||
const li = el("li", { class: "container-row tombstone" });
|
||||
const head = el("div", { class: "head" });
|
||||
head.append(
|
||||
el("span", { class: "name" }, t.name),
|
||||
// `offline` is what the absence of a container proves; `destroyed`
|
||||
// is something the backend cannot know — see the caveat above.
|
||||
el("span", { class: "hive-pill-sm badge-muted" }, "offline"),
|
||||
);
|
||||
if (t.has_creds)
|
||||
head.append(
|
||||
el("span", { class: "hive-pill-sm badge-muted" }, "creds kept"),
|
||||
);
|
||||
head.append(
|
||||
el(
|
||||
"span",
|
||||
{ class: "meta" },
|
||||
`${fmtBytes(t.state_bytes)} · ${fmtAgeDays(t.last_seen)}`,
|
||||
),
|
||||
);
|
||||
li.append(head);
|
||||
|
||||
const actions = el("div", { class: "actions" });
|
||||
actions.append(
|
||||
form(
|
||||
"/api/purge-tombstone/" + t.name,
|
||||
"btn-destroy",
|
||||
"PURG3",
|
||||
"PURGE " +
|
||||
t.name +
|
||||
"? config history, claude creds, " +
|
||||
"and notes are all WIPED. no undo.",
|
||||
{},
|
||||
{ noRefresh: true },
|
||||
),
|
||||
);
|
||||
li.append(actions);
|
||||
ul.append(li);
|
||||
}
|
||||
root.append(ul);
|
||||
}
|
||||
|
||||
/** `tombstones_changed` stream event: replace the list and re-render. */
|
||||
export function applyTombstonesChanged(ev) {
|
||||
tombstonesState = (ev.tombstones || []).slice();
|
||||
renderKept();
|
||||
}
|
||||
Loading…
Reference in a new issue