Watch
0
0
Fork
You've already forked hyperhive
0

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:
iris 2026-10-16 14:03:31 +02:00 • committed by mara
commit 0622736b30
35 changed files with 661 additions and 986 deletions

View file

@ -1,4 +1,4 @@
// /dashboard.html entry point: tab renderers + tab routing + refreshState.
// the dashboard entry point: tab renderers + tab routing + refreshState.
// Reads /api/state on cold load and after every async-form submit; live
// updates run through `applyXxx` mutation handlers triggered by the
// dashboard event stream (subscribed via `openStream` from common.js). See
@ -7,7 +7,7 @@
// Pure helpers (DOM, side panel, path linkification)
// live in `./common.js`; the flow-page surface (broker
// terminal, @-mention composer) lives in `./flow.js` — `flow.html` and
// `dashboard.html` each load their own bundle.
// `index.html` each load their own bundle.
// 4G3NTS (containers) domain lives in `./agents.js`.
import { marked } from "marked";
@ -36,6 +36,13 @@ import {
rebuildCount,
syncBuildsFromSnapshot,
} from "./builds.js";
import {
applyTombstonesChanged,
keptCount,
renderKept,
syncKeptFromSnapshot,
} from "./kept.js";
import { renderLinksMenu } from "./links-menu.js";
// mdNode (in common.js) reads `window.marked` for the markdown side
// panel preview path. Set it here on the dashboard entry so file
@ -44,8 +51,8 @@ window.marked = marked;
(() => {
// ─── async forms ────────────────────────────────────────────────────────
// Shared `data-async` submit interceptor (now in common.js so /core.html's
// bundle gets it too). On success it re-runs refreshState unless the form
// Shared `data-async` submit interceptor (in common.js, shared with the other
// pages' bundles). On success it re-runs refreshState unless the form
// opts out via `data-no-refresh` (its mutation arrives via an SSE event).
bindAsyncForms(() => refreshState());
@ -95,6 +102,22 @@ window.marked = marked;
});
}
// Rev line in the footer: absent when the flake ref isn't a local path
// pin (a bare `github:` url has no on-disk rev to canonicalize), so it
// stays hidden rather than showing a placeholder. `hyperhive_rev`
// canonicalizes to a nix store path (/nix/store/<hash>-<pname>); the
// part that varies between builds is the hash, so slice that out rather
// than truncating from the tail. Falls back to a plain head-slice for a
// non-store path (e.g. a bare local dir during dev).
function renderHiveRev(rev) {
const node = $("hive-rev");
if (!node || !rev) return;
const storeMatch = rev.match(/^\/nix\/store\/([^-]+)/);
node.textContent = `rev ${(storeMatch ? storeMatch[1] : rev).slice(0, 12)}`;
node.title = rev;
node.hidden = false;
}
async function refreshState() {
// Don't yank the form out from under the operator. Try again
// shortly on the next tick; eventually they'll blur and the
@ -144,7 +167,11 @@ window.marked = marked;
// mount's own effect handles the actual fetch.
initJobqRollup();
syncBuildsFromSnapshot(s);
syncKeptFromSnapshot(s);
renderContainers(s);
renderKept();
renderLinksMenu(s);
renderHiveRev(s.hyperhive_rev);
restoreOpenDetails(openDetails);
// No periodic refresh timer. Phase 6 covers every container
// mutation with `ContainerStateChanged` / `ContainerRemoved`
@ -181,7 +208,7 @@ window.marked = marked;
// lost during the disconnect window (same pattern as flow.js's
// onStreamOpen).
//
// Both /dashboard.html and /flow.html subscribe to `/api/dashboard/stream`
// Both the dashboard and /flow.html subscribe to `/api/dashboard/stream`
// and filter client-side — the dashboard ignores broker traffic
// and FL0W ignores mutation events.
const MUTATION_HANDLERS = {
@ -198,6 +225,7 @@ window.marked = marked;
},
meta_inputs_changed: onMetaInputsChanged,
meta_update_running: onMetaUpdateRunning,
tombstones_changed: applyTombstonesChanged,
};
(function bindDashboardStream() {
// Route through the SharedWorker so all open hyperhive tabs on the
@ -208,12 +236,12 @@ window.marked = marked;
// fallback when SharedWorker isn't supported.
//
// kinds= matches MUTATION_HANDLERS verbatim, so the stream carries
// only the 7 wire kinds this page acts on.
// only the 8 wire kinds this page acts on.
const es = openStream(
"/api/dashboard/stream?kinds=" +
"transient_set,transient_cleared," +
"container_state_changed,container_removed,rebuild_queue_changed," +
"meta_inputs_changed,meta_update_running",
"meta_inputs_changed,meta_update_running,tombstones_changed",
);
es.onmessage = (e) => {
let ev;
@ -245,11 +273,11 @@ window.marked = marked;
})();
// ─── tab routing ───────────────────────────────────────────────────────
// Hash-based: `#agents` / `#rebuild` / `#meta` / `#buildlogs`
// Hash-based: `#agents` / `#rebuild` / `#meta` / `#buildlogs` / `#kept`
// activate the matching pane on the dashboard. Empty
// hash defaults to 4G3NTS. FL0W and ST4TS are NOT tabs —
// they're separate pages (`/flow.html`, `/stats.html`) reached from
// the H0M3 hub. Tab routing only
// the pages menu (links-menu.js). Tab routing only
// applies when the tab DOM is present (e.g. not on the flow page
// itself, where these elements don't exist and the loop no-ops).
// The shared hash-routed tab strip (@hive/shared/tabs.js) owns the
@ -299,6 +327,8 @@ window.marked = marked;
setTabCount("agents", stale);
// R3BU1LD QU3U3 — rebuilds in flight.
setTabCount("rebuild", rebuildCount());
// K3PT ST4T3 — entries of kept state.
setTabCount("kept", keptCount());
}
// Poll the state stores on a 1s tick to keep the pill counts in
// sync. The state stores are mutated synchronously by every SSE