Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e455964cb3 | ||
|
|
ee5c057d8f |
29 changed files with 448 additions and 517 deletions
|
|
@ -127,7 +127,7 @@ Two things live in the `agent-configs` Forgejo organization:
|
|||
[How a hive reads the forge](#how-a-hive-reads-the-forge)).
|
||||
- The dashboard links each container's deployed config commit to this
|
||||
config repo on the swarm's forge (`swarm.forge.publicUrl`; `null` means no
|
||||
link), so operators can go straight from the SW4RM tab into
|
||||
link), so operators can go straight from the 4G3NTS tab into
|
||||
the rendered repo without an extra `git` step.
|
||||
|
||||
The `hive-forge` CLI (separate workspace crate, see
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ swarm controller and hive-c0re each build their own graph on it.
|
|||
|
||||
Two views, one per graph, drawn by the same component: the swarm UI's
|
||||
`/jobs` page shows the swarm controller's graph, and the hive dashboard's
|
||||
**BU1LDS** page (R3BU1LD QU3U3 — see
|
||||
**R3BU1LD QU3U3** tab (see
|
||||
[`web-ui/dashboard.md`](../web-ui/dashboard.md)) shows that hive's. Each
|
||||
**row** is one job; the rows nested under it are that job's steps, in
|
||||
order (occasionally a couple run side by side). A step shows one of:
|
||||
|
|
|
|||
|
|
@ -11,21 +11,23 @@ what you actually do here.
|
|||
## Where things are
|
||||
|
||||
Everything starts at the **H0M3 hub**, served at `/` — a grid of tiles
|
||||
linking to every surface (Dashboard, Flow, Logs, Builds, Stats,
|
||||
linking to every surface (Dashboard, Flow, Logs, Stats,
|
||||
Settings, Core). Every page links back to H0M3, so you're
|
||||
never more than one step from the hub.
|
||||
|
||||
The **dashboard** itself (`/dashboard.html`) is where you'll spend most
|
||||
of your time. It's a single page with exactly two tabs:
|
||||
of your time. It's a single page with five tabs:
|
||||
|
||||
- **SW4RM** — every agent, live. This is the default tab and the one
|
||||
- **4G3NTS** — every agent, live. This is the default tab and the one
|
||||
you'll check most.
|
||||
- **R3BU1LD QU3U3**, **M3T4 1NPUTS** and **BUILD L0GS** — the build
|
||||
lifecycle: the rebuild queue with a live log of whatever is building,
|
||||
the meta flake inputs you can bump, and the build log history.
|
||||
- **P3RM1SS10NS** — what tools and system-level access each agent has.
|
||||
|
||||
Everything else lives on its own page instead, all reachable from the
|
||||
H0M3 hub: **Flow** (`/flow.html`, the raw live message stream across
|
||||
the whole swarm), **Stats** (`/stats.html`, swarm-wide usage stats),
|
||||
**Builds** (`/builds.html`, the rebuild queue and build history), and
|
||||
the whole swarm), **Stats** (`/stats.html`, swarm-wide usage stats), and
|
||||
**Core** (`/core.html`, tombstones and container resource use).
|
||||
|
||||
Each agent also has its own page — a full terminal view of that
|
||||
|
|
@ -35,7 +37,7 @@ if the gateway isn't in front).
|
|||
|
||||
## The things you'll actually do
|
||||
|
||||
**Check on an agent.** SW4RM shows every container as a row: name,
|
||||
**Check on an agent.** 4G3NTS shows every container as a row: name,
|
||||
whether it's running, what it's currently doing (a live status pill —
|
||||
`rebuilding…`, `starting…`, or similar — while something's in flight),
|
||||
and quick links (stats, screen, forge profile). Select the name to open
|
||||
|
|
@ -47,12 +49,12 @@ repo. Review and merge them on the forge; the merge deploys the change.
|
|||
See [`config-changes.md`](../agent-lifecycle/config-changes.md#config-changes).
|
||||
|
||||
**Start, stop, restart, or rebuild an agent.** Select one or more
|
||||
agents on SW4RM (select the icon) and use the selection bar, or use the
|
||||
agents on 4G3NTS (select the icon) and use the selection bar, or use the
|
||||
per-agent `⋮` menu on a single row. Rebuilding re-applies that agent's
|
||||
current config; use it whenever an agent
|
||||
shows as "needs update."
|
||||
|
||||
**Watch a build.** BU1LDS shows the rebuild queue live, plus a
|
||||
**Watch a build.** The dashboard's R3BU1LD QU3U3 tab shows the rebuild queue live, plus a
|
||||
streaming log of whatever's currently building. Useful right after
|
||||
merging a config change or bumping a flake input.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ matrix accounts, the controller's job graph — are on the
|
|||
|
||||
hive-c0re serves the dashboard at `/dashboard.html` (with the home page at `/`).
|
||||
It has a fixed chrome header at the top and a `<main>` that shows exactly
|
||||
one tab pane at a time. The URL hash (`#swarm`,
|
||||
`#permissions`) drives which pane is
|
||||
one tab pane at a time. The URL hash (`#agents`, `#rebuild`, `#meta`,
|
||||
`#buildlogs`, `#permissions`) drives which pane is
|
||||
active; hash changes don't reload the page. FL0W and the other
|
||||
standalone pages are reachable from the H0M3 hub at `/`, not
|
||||
from the dashboard tab strip.
|
||||
|
|
@ -19,17 +19,18 @@ from the dashboard tab strip.
|
|||
**Chrome header** (fixed, overlays the active tab pane):
|
||||
- **← home back-link**: top-left of the chrome, links to the H0M3 hub
|
||||
at `/`. Every surface links back to the hub rather than to each other.
|
||||
- **Tab strip**: `◆ SW4RM ◆`, `◆ P3RM1SS10NS ◆`. In-page tabs only —
|
||||
- **Tab strip**: `◆ 4G3NTS ◆`, `◆ R3BU1LD QU3U3 ◆`, `◆ M3T4 1NPUTS ◆`,
|
||||
`◆ BUILD L0GS ◆`, `◆ P3RM1SS10NS ◆`. In-page tabs only —
|
||||
the SYST3M panels live on the
|
||||
standalone **C0R3** page (`/core.html`), and FL0W / ST4TS
|
||||
live on their own pages too, all reachable from the
|
||||
H0M3 hub (not the tab strip). Count pill on SW4RM
|
||||
(container count).
|
||||
H0M3 hub (not the tab strip). Count pills on 4G3NTS
|
||||
(stale containers) and R3BU1LD QU3U3 (rebuilds in flight).
|
||||
- **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`)
|
||||
— sits below the tab strip.
|
||||
- **Server-warnings banner** — a generic, sticky top-of-page strip shown
|
||||
on **every** page (dashboard + every stand-alone page — FL0W,
|
||||
H0M3, C0R3, BU1LDS, ST4TS), injected at the top
|
||||
H0M3, C0R3, ST4TS), injected at the top
|
||||
of `<body>` by `renderServerWarnings` in `common.js`. Driven by
|
||||
`state.server_warnings` — a list of `{ kind, level, message }` — and
|
||||
coloured by `level` (`warn` amber / `crit` red). hive-c0re owns the
|
||||
|
|
@ -55,7 +56,7 @@ The FL0W page uses a slim header (a `← home` back-link + the page
|
|||
title) rather than the dashboard tab strip — it's a standalone surface,
|
||||
not a tab pane.
|
||||
|
||||
## SW4RM tab
|
||||
## 4G3NTS tab
|
||||
|
||||
**C0NTAINERS** — live containers as a flat list, one row per
|
||||
container, sorted alphabetically by agent name; every container the
|
||||
|
|
@ -73,13 +74,13 @@ agent is stale.
|
|||
|
||||
Passive / rare-interaction state, reached from the **Core** tile on the
|
||||
H0M3 hub (served at
|
||||
`/core.html`), with the same minimal chrome as `/builds.html`: a `← home`
|
||||
back-link and a single pane, **K3PT ST4T3**. The page is its own esbuild
|
||||
bundle (`core.js`) that cold-loads `/api/state` and subscribes to
|
||||
`/api/dashboard/stream` for `tombstones_changed`, `capabilities_changed`,
|
||||
and `tool_groups_changed` (the latter two re-render the stale-perms
|
||||
sub-section when permission data changes). (Rebuild queue and meta inputs
|
||||
live on the BU1LDS page — see below.)
|
||||
`/core.html`), with minimal chrome: a `← home` back-link and a single pane,
|
||||
**K3PT ST4T3**. The page is its own esbuild bundle (`core.js`) that
|
||||
cold-loads `/api/state` and subscribes to `/api/dashboard/stream` for
|
||||
`tombstones_changed`, `capabilities_changed`, and `tool_groups_changed`
|
||||
(the latter two re-render the stale-perms sub-section when permission
|
||||
data changes). (Rebuild queue and meta inputs are dashboard tabs — see
|
||||
below.)
|
||||
|
||||
Per-container CPU, memory and disk load isn't shown in the hive UI. The
|
||||
same figures go out as OTEL metrics and appear in Grafana (see
|
||||
|
|
@ -111,21 +112,23 @@ autorefreshes on `capabilities_changed` + `tool_groups_changed` SSE
|
|||
events. Each ghost agent gets a `✕ clear perms` button
|
||||
(`DELETE /api/permissions/{name}`).
|
||||
|
||||
## BU1LDS page (`/builds.html`)
|
||||
## Build tabs (`#rebuild`, `#meta`, `#buildlogs`)
|
||||
|
||||
The build lifecycle hub — rebuild queue, live build log, meta inputs,
|
||||
and build log history on one page. Standalone page reached from the
|
||||
**Builds** tile on the H0M3 hub, same minimal chrome as `/core.html`.
|
||||
Three sub-tabs: **R3BU1LD QU3U3** (default), **M3T4 1NPUTS**,
|
||||
**BUILD L0GS**. Its own esbuild bundle (`builds.js`); cold-loads
|
||||
`/api/state` and subscribes to `/api/dashboard/stream` for
|
||||
`rebuild_queue_changed`, `meta_inputs_changed`, `meta_update_running`.
|
||||
The SW4RM tab (`/dashboard.html`) does **not** read this endpoint at
|
||||
all, by design — the swarm view stays independent of job-queue
|
||||
internals. Its per-agent pending badges are transient-only, and
|
||||
its queue-summary banner reads the much narrower `GET /api/jobq/rollup`
|
||||
instead (see Container row, below) — a handful of pre-tallied counts,
|
||||
not the graph.
|
||||
The build lifecycle — rebuild queue, live build log, meta inputs, and
|
||||
build log history — is three tabs of the dashboard: **R3BU1LD QU3U3**
|
||||
(`#rebuild`), **M3T4 1NPUTS** (`#meta`) and **BUILD L0GS**
|
||||
(`#buildlogs`). They live in `builds.js`, which `tabs.js` imports: the
|
||||
dashboard's one tab strip, `/api/state` snapshot and
|
||||
`/api/dashboard/stream` subscription serve them, with
|
||||
`rebuild_queue_changed`, `meta_inputs_changed` and `meta_update_running`
|
||||
on the stream alongside the dashboard's other kinds. The count pill on
|
||||
R3BU1LD QU3U3 is the number of rebuilds in flight.
|
||||
The 4G3NTS tab does **not** read the graph endpoint, by design — the swarm
|
||||
view stays independent of job-queue internals. Its per-agent pending
|
||||
badges are transient-only, and its queue-summary banner reads the much
|
||||
narrower `GET /api/jobq/rollup` instead (see Container row, below) — a
|
||||
handful of pre-tallied counts, not the graph. The banner's "view queue →"
|
||||
link goes to the `#rebuild` tab.
|
||||
|
||||
**R3BU1LD QU3U3** — pending, in-flight, and recently settled container
|
||||
operations: rebuilds, meta-update cascades, and first-spawns. One
|
||||
|
|
@ -394,7 +397,7 @@ re-renders the terminal row. The operator addresses the root agent as `@root`.
|
|||
## H0M3 page (`/`)
|
||||
|
||||
The H0M3 hub is the primary landing page (served at `/` by default). A
|
||||
responsive grid of link tiles — Dashboard, Flow, Builds, Stats,
|
||||
responsive grid of link tiles — Dashboard, Flow, Stats,
|
||||
Core, API — each pointing to their respective
|
||||
surfaces, all unconditionally shown (no gating). The page is a pure
|
||||
portal with no tab-bar or SSE subscriptions. Typography + colours inherit
|
||||
|
|
@ -411,7 +414,7 @@ the tooltip says "select … for bulk actions" or "deselect … (or press
|
|||
Esc to clear all)." The `<img>` points at `<url>/icon`; load failure
|
||||
falls back to the dimmed hyperhive mark (`/favicon.svg`). The card
|
||||
body sits to the right with three stacked lines
|
||||
(`assets/swarm.js::renderContainers`).
|
||||
(`assets/agents.js::renderContainers`).
|
||||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
**Icon layout + load strategy:** the `<img>` is absolutely
|
||||
|
|
@ -580,10 +583,10 @@ agent is stale. Banner pulses on each broker SSE event
|
|||
**Build-queue summary banner** — when the job queue has any active
|
||||
work, a compact amber banner sits above the container list: `◐ build
|
||||
queue — N running · M queued — view queue →` (the link goes to the
|
||||
BU1LDS page's R3BU1LD QU3U3). The shared `JobqRollup` Preact component
|
||||
R3BU1LD QU3U3 tab). The shared `JobqRollup` Preact component
|
||||
(`@hive/shared/jobq-rollup.js` — the same one swarm-ui's `/jobs` page
|
||||
mounts, pointed at swarm-controller's own rollup endpoint instead),
|
||||
mounted once into `#jobq-rollup-section` by `swarm.js::initJobqRollup`
|
||||
mounted once into `#jobq-rollup-section` by `agents.js::initJobqRollup`
|
||||
and refreshed via its own handle rather than being re-rendered by
|
||||
`renderContainers`. Reads `GET /api/jobq/rollup`
|
||||
(`hive-jobq-wire::state_rollup`) — `Vec<{ state, nodes, roots }>`,
|
||||
|
|
@ -645,7 +648,7 @@ Bulk actions (`R3ST4RT` / `ST0P` / `ST4RT` / `P4US3` / `R3SUM3` /
|
|||
buttons — see **Container row** above. Selecting an agent's icon
|
||||
toggles its selection (an in-memory `Set<name>`); `Esc` or the bar's
|
||||
`✕ clear` button drops everything. The selection persists across tab
|
||||
switches in-memory — the bar just hides on non-SW4RM tabs since other
|
||||
switches in-memory — the bar just hides on non-4G3NTS tabs since other
|
||||
tabs don't show the agent cards needed to cross-reference.
|
||||
|
||||
When the operator selects one or more agents (via the icon), a sticky
|
||||
|
|
@ -711,11 +714,11 @@ below — some endpoints aren't in it yet.
|
|||
404 when the row is missing (vacuum-reaped or stale id).
|
||||
- `GET /api/build-logs/id/{id}/stream` — live SSE stream of
|
||||
stdout+stderr for an in-progress build (newline-delimited JSON
|
||||
frames). Used by the expanding live-row view in BU1LDS. The
|
||||
frames). Used by the expanding live-row view in the BUILD L0GS tab. The
|
||||
stream closes when the build finishes or errors.
|
||||
- `GET /api/build-logs/id/{id}/raw` — raw concatenated
|
||||
stdout+stderr as `text/plain`. Used by the `↓ raw` / `↓ download
|
||||
raw` links in BU1LDS. Works on both finished and in-progress
|
||||
raw` links in the build tabs. Works on both finished and in-progress
|
||||
build logs.
|
||||
- `GET /static/marked.js` serves the vendored `marked` bundle used
|
||||
for markdown previews.
|
||||
|
|
@ -854,10 +857,10 @@ payload):
|
|||
full snapshot of destroyed-but-state-kept agents. Emitted on
|
||||
destroy, purge, and revive. C0R3 K3PT ST4T3 tab subscribes.
|
||||
- `meta_inputs_changed` (seq, inputs: `Vec<MetaInput>`) — full
|
||||
snapshot of `meta/flake.lock` inputs. BU1LDS M3T4 1NPUTS tab
|
||||
snapshot of `meta/flake.lock` inputs. The M3T4 1NPUTS tab
|
||||
subscribes; same shape as `GET /api/meta-inputs`.
|
||||
- `meta_update_running` (running: bool) — emitted when a
|
||||
`nix flake update` ripple starts or completes. BU1LDS M3T4 1NPUTS
|
||||
`nix flake update` ripple starts or completes. The M3T4 1NPUTS
|
||||
tab uses this to show/hide the "⏳ meta-update running" banner.
|
||||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
CSS bundle per page (`colors.css` + `theme.css` + `common.css`,
|
||||
loaded by every page, plus a page-specific bundle —
|
||||
`dashboard.css` / `flow.css` / `home.css` /
|
||||
`stats.css` / `core.css` / `builds.css`);
|
||||
`stats.css` / `core.css`);
|
||||
`common.css` inlines `@hive/shared`'s
|
||||
`base.css` + `terminal.css` + `tabs.css` + `chrome.css` +
|
||||
`pill.css` via esbuild's `@import` resolution.
|
||||
|
|
@ -208,7 +208,7 @@ re-render), call `refreshState()`. State shapes live in
|
|||
`hive-c0re/src/dashboard/state_snapshot.rs::StateSnapshot` and
|
||||
`web_ui/state.rs::StateSnapshot` — when
|
||||
adding state fields, plumb through the snapshot struct and the
|
||||
relevant domain module (`swarm.js`, `permissions.js`, etc.) render function.
|
||||
relevant domain module (`agents.js`, `permissions.js`, etc.) render function.
|
||||
|
||||
**Focus preservation:** `refreshState` checks whether
|
||||
`document.activeElement` sits inside one of the managed sections
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ layers per-agent additions on top of the default agent dist.
|
|||
output is reproducible without network access at build time.
|
||||
- **esbuild**: `agent` and `swarm-ui` are Preact apps; `dashboard` is a
|
||||
vanilla-JS + custom-element MPA with a few Preact-rendered pages
|
||||
(`builds.js`, `swarm.js`); `shared` holds both kinds of module. No
|
||||
(`builds.js`, `agents.js`, imported by the dashboard entry); `shared` holds both kinds of module. No
|
||||
framework runtime beyond Preact where it's used. Each workspace's
|
||||
`build.mjs` is a short esbuild script.
|
||||
- **Single-PR migration**: see the tracked design proposal and
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// esbuild build for @hive/dashboard: one JS+CSS bundle per page (H0M3 at
|
||||
// `/`, dashboard/flow/core/builds at their own
|
||||
// `/`, dashboard/flow/core/stats at their own
|
||||
// `.html`), each named after its own entry point below — see the
|
||||
// `entryPoints`/`for` lists for the exact map, not repeated here to
|
||||
// avoid this comment drifting out of sync with the real build steps.
|
||||
|
|
@ -38,7 +38,6 @@ await build({
|
|||
src("home.js"),
|
||||
src("stats.js"),
|
||||
src("core.js"),
|
||||
src("builds.js"),
|
||||
],
|
||||
outdir: staticDir(""),
|
||||
bundle: true,
|
||||
|
|
@ -57,7 +56,7 @@ await build({
|
|||
// runs as its own esbuild invocation over different entry points.
|
||||
loader: { ".css": "text" },
|
||||
// `@hive/shared/jobq-graph.js` resolves to a real `.jsx` file
|
||||
// (`JobqGraph.jsx`), pulled in transitively by `builds.js` — esbuild
|
||||
// (`JobqGraph.jsx`), pulled in transitively by `tabs.js` via `builds.js` — esbuild
|
||||
// already picks the `jsx` loader for `.jsx` by extension, this just
|
||||
// sets the transform mode to match swarm-ui's (which also authors
|
||||
// this file). No other entry here uses JSX today; this doesn't turn
|
||||
|
|
@ -103,7 +102,6 @@ for (const entry of [
|
|||
"home.css",
|
||||
"stats.css",
|
||||
"core.css",
|
||||
"builds.css",
|
||||
]) {
|
||||
await build({
|
||||
entryPoints: [src(entry)],
|
||||
|
|
@ -120,7 +118,6 @@ for (const html of [
|
|||
"flow.html",
|
||||
"stats.html",
|
||||
"core.html",
|
||||
"builds.html",
|
||||
]) {
|
||||
copyFileSync(src(html), dist(html));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// hive-agent-menu.js — <hive-agent-menu>, the per-agent "⋮" context-menu
|
||||
// custom element. One instance per agent card, appended after
|
||||
// .card-body in the container row's flex layout (swarm.js's
|
||||
// .card-body in the container row's flex layout (agents.js's
|
||||
// buildContainerLi via buildAgentMenu).
|
||||
//
|
||||
// Deliberately a *light-DOM* element with no shadow root of its own: all
|
||||
|
|
@ -66,7 +66,7 @@ async function agentMenuPost(actionPath, name, body, flags) {
|
|||
class HiveAgentMenu extends HTMLElement {
|
||||
connectedCallback() {
|
||||
// Same reconnect-without-detach hazard as <hive-menu> (see its
|
||||
// connectedCallback comment) — swarm.js's row cache can move an
|
||||
// connectedCallback comment) — agents.js's row cache can move an
|
||||
// already-built <li> subtree without a real detach. Without this
|
||||
// guard a reconnect would append a second <hive-menu> on top of the
|
||||
// first, doubling the dropdown.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SW4RM (containers) domain — extracted from tabs.js.
|
||||
// 4G3NTS (containers) domain — extracted from tabs.js.
|
||||
// Agent topology, container-row rendering, selection bar, and all
|
||||
// live-update apply handlers for container-state, transient, and
|
||||
// job-queue-rollup ops. See docs/web-ui/dashboard.md::Container row for the
|
||||
|
|
@ -52,7 +52,7 @@ const selectionState = new Set();
|
|||
// ─── job queue rollup (queue-summary banner only) ──────────────────────────
|
||||
//
|
||||
// The ONE piece of job-queue-derived state on this page, and deliberately
|
||||
// narrow — mara: "swarm.js should not need to pull in the jobq to do its
|
||||
// narrow — mara: "agents.js should not need to pull in the jobq to do its
|
||||
// job." Rendering is owned by the shared `JobqRollup` Preact component
|
||||
// (same one swarm-ui's /jobs page mounts, self-fetching
|
||||
// GET /api/jobq/rollup); this file just calls `render(h(...))` and bumps
|
||||
|
|
@ -70,7 +70,7 @@ export function initJobqRollup() {
|
|||
render(
|
||||
h(JobqRollup, {
|
||||
endpoint: "/api/jobq/rollup",
|
||||
queueHref: "/builds.html",
|
||||
queueHref: "#rebuild",
|
||||
refreshToken: jobqRollupToken,
|
||||
}),
|
||||
root,
|
||||
|
|
@ -80,8 +80,7 @@ export function initJobqRollup() {
|
|||
// `rebuild_queue_changed` fires — a payload-less push trigger by
|
||||
// design, confirmed with atlas on the jobq-deletion tracker: the event
|
||||
// carries no `queue` field this page reads, same "something changed,
|
||||
// go refetch" treatment builds.js already gives its own JobqGraph mount
|
||||
// handle's .refresh().
|
||||
// go refetch" treatment builds.js gives the R3BU1LD QU3U3 tab's JobqGraph.
|
||||
export function applyRebuildQueueChanged() {
|
||||
jobqRollupToken += 1;
|
||||
initJobqRollup();
|
||||
|
|
@ -753,7 +752,7 @@ export function renderContainers(s) {
|
|||
}
|
||||
|
||||
// ─── selection bar ───────────────────────────────────────────────────────────
|
||||
// Sticky-bottom strip; visible when >=1 agent selected on the SW4RM
|
||||
// Sticky-bottom strip; visible when >=1 agent selected on the 4G3NTS
|
||||
// tab. See docs/web-ui/dashboard.md::Selection bar for the interaction model
|
||||
// and the per-action availability rules (disabled-with-tooltip for
|
||||
// actions that don't apply to the full selection). Actions POST per
|
||||
|
|
@ -768,11 +767,12 @@ export function renderSelectionBar(containers) {
|
|||
if (!countSpan || !namesSpan || !actions) return;
|
||||
|
||||
const selected = containers.filter((c) => selectionState.has(c.name));
|
||||
// Tab-gate: bar only renders on SW4RM (the only tab with agent
|
||||
// Tab-gate: bar only renders on 4G3NTS (the only tab with agent
|
||||
// cards to cross-reference). Selection state lives in-memory and
|
||||
// the bar reappears on return to SW4RM if still non-empty.
|
||||
const onSwarmTab = (document.body.dataset.activeTab || "swarm") === "swarm";
|
||||
if (!selected.length || !onSwarmTab) {
|
||||
// the bar reappears on return to 4G3NTS if still non-empty.
|
||||
const onAgentsTab =
|
||||
(document.body.dataset.activeTab || "agents") === "agents";
|
||||
if (!selected.length || !onAgentsTab) {
|
||||
bar.hidden = true;
|
||||
document.body.classList.remove("has-selection");
|
||||
return;
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
/* ─── /builds.html — BU1LDS page ────────────────────────────────────
|
||||
The build lifecycle hub: rebuild queue + live log, meta inputs, and
|
||||
build log history on one page.
|
||||
|
||||
Imports system-sections.css for rebuild queue + meta inputs + live-log
|
||||
component styles; the build-logs-* rules for the BUILD L0GS list are
|
||||
below. */
|
||||
@import "./system-sections.css";
|
||||
@import "@hive/shared/jobq-graph.css";
|
||||
|
||||
body.builds-shell {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.builds-tabbar {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.builds-main {
|
||||
padding: 1.2em 1.5em 2em;
|
||||
}
|
||||
|
||||
.builds-pane[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logs-toolbar {
|
||||
display: flex;
|
||||
gap: 0.6em;
|
||||
align-items: center;
|
||||
margin-bottom: 0.9em;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ─── build-log list ──────────────────────────────────────────────
|
||||
Scrollable list of log header rows; clicking a row expands full
|
||||
stdout+stderr inline. */
|
||||
.build-logs-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
}
|
||||
.build-logs-item {
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0.15em 0;
|
||||
}
|
||||
.build-logs-row-btn {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4em;
|
||||
align-items: baseline;
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--fg);
|
||||
font-family: inherit;
|
||||
font-size: 0.85em;
|
||||
text-align: left;
|
||||
padding: 0.25em 0.4em;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.build-logs-row-btn:hover {
|
||||
background: var(--bg-elev);
|
||||
}
|
||||
.build-logs-kind {
|
||||
font-weight: 600;
|
||||
}
|
||||
.build-logs-age {
|
||||
font-size: 0.88em;
|
||||
}
|
||||
.build-logs-cmdline {
|
||||
color: var(--muted);
|
||||
font-size: 0.82em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 40em;
|
||||
}
|
||||
.build-logs-detail {
|
||||
padding: 0 0.4em 0.4em;
|
||||
}
|
||||
.build-logs-output {
|
||||
margin: 0.3em 0 0;
|
||||
padding: 0.4em 0.6em;
|
||||
background: var(--crust);
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--purple-dim);
|
||||
font-size: 0.82em;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow-x: auto;
|
||||
max-height: 30em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.build-logs-dl {
|
||||
color: var(--muted);
|
||||
font-size: 0.8em;
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--purple-dim);
|
||||
padding: 0.1em 0.4em;
|
||||
border-radius: 2px;
|
||||
margin-top: 0.3em;
|
||||
display: none;
|
||||
}
|
||||
.build-logs-dl:not([hidden]) {
|
||||
display: inline-block;
|
||||
}
|
||||
.build-logs-dl:hover {
|
||||
color: var(--fg);
|
||||
border-color: var(--purple-dim);
|
||||
}
|
||||
|
||||
.build-logs-live-badge {
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
|
||||
@keyframes live-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.45;
|
||||
}
|
||||
}
|
||||
.build-logs-live-badge.badge-running {
|
||||
animation: live-pulse 1.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.build-logs-runtime {
|
||||
font-size: 0.85em;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>hyperhive // BU1LDS</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="stylesheet" href="/static/colors.css" />
|
||||
<link rel="stylesheet" href="/static/theme.css" />
|
||||
<link rel="stylesheet" href="/static/common.css" />
|
||||
<link rel="stylesheet" href="/static/builds.css" />
|
||||
</head>
|
||||
<body class="builds-shell">
|
||||
<!-- BU1LDS: the build lifecycle hub — rebuild queue, live build log,
|
||||
meta inputs, and build log history on one page. Same minimal
|
||||
chrome as /core.html — a `← home` back-link + a <hive-tab-strip>
|
||||
sub-tab nav. Default sub-tab is the rebuild queue. -->
|
||||
<header class="page-header">
|
||||
<a class="page-back" href="/">← home</a>
|
||||
<hive-tab-strip
|
||||
class="hive-tabbar builds-tabbar"
|
||||
id="builds-tabbar"
|
||||
prefix="builds"
|
||||
role="tablist"
|
||||
></hive-tab-strip>
|
||||
</header>
|
||||
|
||||
<main class="builds-main">
|
||||
<!-- R3BU1LD QU3U3: pending + running rebuilds, meta-updates, and
|
||||
first-spawns. Rendered from GET /api/jobq/graph by the
|
||||
`JobqGraph` Preact component (@hive/shared/jobq-graph.js);
|
||||
`rebuild_queue_changed` over /api/dashboard/stream is the
|
||||
refresh trigger and carries no payload of its own. Default tab. -->
|
||||
<section
|
||||
class="builds-pane"
|
||||
id="builds-pane-rebuild"
|
||||
data-tab-pane="rebuild"
|
||||
role="tabpanel"
|
||||
aria-labelledby="builds-tab-rebuild"
|
||||
>
|
||||
<p class="meta">
|
||||
pending + running rebuilds, meta-updates, and first-spawns. one runs
|
||||
at a time; meta-update cascades nest under their parent. dedup:
|
||||
re-enqueueing a still-queued op collapses into the existing entry.
|
||||
</p>
|
||||
<div id="rebuild-queue-section">
|
||||
<p class="meta">loading…</p>
|
||||
</div>
|
||||
<!-- Live build log of the currently-running rebuild (one runs at a
|
||||
time). Managed by renderRebuildLiveLog in builds.js, separate from
|
||||
rebuild-queue-section so the queue's row re-render never disturbs
|
||||
the open SSE stream. -->
|
||||
<div id="rebuild-live-log" class="rebuild-live-log" hidden></div>
|
||||
</section>
|
||||
|
||||
<!-- M3T4 1NPUTS: select inputs to nix flake update in /meta/. -->
|
||||
<section
|
||||
class="builds-pane"
|
||||
id="builds-pane-meta"
|
||||
data-tab-pane="meta"
|
||||
role="tabpanel"
|
||||
aria-labelledby="builds-tab-meta"
|
||||
>
|
||||
<p class="meta">
|
||||
select inputs to <code>nix flake update</code> in <code>/meta/</code>.
|
||||
selected agents rebuild in sequence after the lock bump; the
|
||||
submitting agent learns each outcome via the usual
|
||||
<code>rebuilt</code> system event.
|
||||
</p>
|
||||
<div id="meta-inputs-section">
|
||||
<p class="meta">loading…</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- BUILD L0GS: all-agents build log history.
|
||||
Click a row to expand stdout + stderr. Live builds stream in real
|
||||
time. Lazy-loaded on first tab activation; auto-refreshes when
|
||||
rebuild_queue_changed fires. Deep-link: ?id=N#buildlogs. -->
|
||||
<section
|
||||
class="builds-pane"
|
||||
id="builds-pane-buildlogs"
|
||||
data-tab-pane="buildlogs"
|
||||
role="tabpanel"
|
||||
aria-labelledby="builds-tab-buildlogs"
|
||||
>
|
||||
<p class="meta">
|
||||
all-agents build log history. click a row to expand stdout + stderr.
|
||||
live builds stream in real time.
|
||||
</p>
|
||||
<div class="logs-toolbar">
|
||||
<button type="button" class="btn btn-restart" id="build-refresh">
|
||||
↻ refresh
|
||||
</button>
|
||||
</div>
|
||||
<div id="build-list"><p class="meta">loading…</p></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script type="module" src="/static/builds.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,23 +1,17 @@
|
|||
// BU1LDS page entry (/builds.html). The build lifecycle hub — rebuild
|
||||
// queue, live build log, meta inputs, and build log history on one page.
|
||||
// Three sub-tabs via <hive-tab-strip> (default = Rebuild Queue):
|
||||
// - R3BU1LD QU3U3: pending + running rebuilds + live build log inline
|
||||
// - M3T4 1NPUTS: nix flake input selector + meta-update trigger
|
||||
// - BUILD L0GS: all-agents build log history (lazy-loaded on tab show)
|
||||
// The build lifecycle tabs of /dashboard.html — rebuild queue, live build
|
||||
// log, meta inputs, and build log history. Not an entry point: tabs.js
|
||||
// imports it, owns the tab strip and the dashboard event stream, and calls
|
||||
// the exports below. Three tabs, the panes' ids matching the tab ids:
|
||||
// - rebuild (R3BU1LD QU3U3): pending + running rebuilds + live build log
|
||||
// - meta (M3T4 1NPUTS): nix flake input selector + meta-update
|
||||
// - buildlogs (BUILD L0GS): all-agents build log history, lazy-loaded
|
||||
// the first time the tab is shown
|
||||
|
||||
import {
|
||||
$,
|
||||
fmtAgeSecs,
|
||||
openStream,
|
||||
openBuildLogStream,
|
||||
initServerWarnings,
|
||||
} from "./common.js";
|
||||
import { $, fmtAgeSecs, openBuildLogStream } from "./common.js";
|
||||
import { el } from "@hive/shared/dom.js";
|
||||
import { bindAsyncForms } from "@hive/shared/forms.js";
|
||||
import { themedConfirm } from "@hive/shared/modal.js";
|
||||
import { h, render } from "preact";
|
||||
import { fmtAgo, fmtDuration, truncate } from "./util.js";
|
||||
import "@hive/shared/hive-tab-strip.js";
|
||||
import { JobqGraph } from "@hive/shared/jobq-graph.js";
|
||||
|
||||
// ─── derived state ───────────────────────────────────────────────────────────
|
||||
|
|
@ -174,7 +168,7 @@ function renderMetaInputs(s) {
|
|||
// the generic component's.
|
||||
// Split from `renderRebuildQueue` below: `replaceChildren()` only
|
||||
// belongs on the *first* render, to clear the static "loading…"
|
||||
// placeholder `#rebuild-queue-section` starts with in `builds.html` —
|
||||
// placeholder `#rebuild-queue-section` starts with in `dashboard.html` —
|
||||
// Preact doesn't know that placeholder is there, so a later render
|
||||
// call must NOT repeat `replaceChildren()` (it would wipe Preact's own
|
||||
// tracked children out from under its diffing rather than let it
|
||||
|
|
@ -196,7 +190,6 @@ function renderRebuildQueue() {
|
|||
onUpdate: (nodes) => {
|
||||
jobqNodes = nodes || [];
|
||||
renderRebuildLiveLog();
|
||||
updateRebuildCount();
|
||||
},
|
||||
onCancel: async (id) => {
|
||||
const node = jobqNodes.find((n) => n.id === id);
|
||||
|
|
@ -384,33 +377,18 @@ function renderRebuildLiveLog() {
|
|||
}
|
||||
|
||||
// ─── rebuild-queue count pill ─────────────────────────────────────────────────
|
||||
function updateRebuildCount() {
|
||||
const pill = $("builds-tab-count-rebuild");
|
||||
if (!pill) return;
|
||||
// Pending/Running/Finishing = in flight (Finishing = own work done, a
|
||||
// sub-node still running — still counts). Root nodes only: each is one
|
||||
// queue entry.
|
||||
const n = jobqNodes.filter(
|
||||
// The queue's in-flight count, for the R3BU1LD QU3U3 tab's pill (tabs.js
|
||||
// reads it on its refresh tick). Pending/Running/Finishing = in flight
|
||||
// (Finishing = own work done, a sub-node still running — still counts).
|
||||
// Root nodes only: each is one queue entry.
|
||||
export function rebuildCount() {
|
||||
return jobqNodes.filter(
|
||||
(n) =>
|
||||
n.parent == null &&
|
||||
(n.state === "Pending" ||
|
||||
n.state === "Running" ||
|
||||
n.state === "Finishing"),
|
||||
).length;
|
||||
if (n > 0) {
|
||||
pill.textContent = String(n);
|
||||
pill.hidden = false;
|
||||
} else {
|
||||
pill.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── render-all (cold load + any full re-render) ──────────────────────────────
|
||||
function renderAll() {
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
// Rebuild-queue rendering + the count-pill/live-log it drives all happen
|
||||
// off JobqGraph's own onUpdate callback (see mountRebuildQueue) —
|
||||
// nothing to render here directly.
|
||||
}
|
||||
|
||||
// ─── BUILD L0GS tab ───────────────────────────────────────────────────────────
|
||||
|
|
@ -419,10 +397,7 @@ function renderAll() {
|
|||
// 2s) when rebuild_queue_changed fires while the BUILD L0GS tab is active.
|
||||
// Deep-link: ?id=N#buildlogs auto-expands the target row.
|
||||
|
||||
const buildList = $("build-list");
|
||||
const buildRefresh = $("build-refresh");
|
||||
let buildLogsLoaded = false;
|
||||
let buildTabs; // set in init
|
||||
|
||||
function fmtTs(unixSecs) {
|
||||
if (!unixSecs) return "";
|
||||
|
|
@ -431,6 +406,7 @@ function fmtTs(unixSecs) {
|
|||
}
|
||||
|
||||
async function fetchBuild() {
|
||||
const buildList = $("build-list");
|
||||
if (!buildList) return;
|
||||
buildList.replaceChildren();
|
||||
buildList.append(el("p", { class: "meta" }, "loading…"));
|
||||
|
|
@ -612,93 +588,54 @@ async function fetchBuild() {
|
|||
}
|
||||
}
|
||||
|
||||
if (buildRefresh) buildRefresh.addEventListener("click", fetchBuild);
|
||||
// ─── entry points for tabs.js ─────────────────────────────────────────────────
|
||||
// Everything below is called by tabs.js, which owns the tab strip, the
|
||||
// `/api/state` snapshot and the dashboard event stream.
|
||||
|
||||
// ─── SSE handlers ─────────────────────────────────────────────────────────────
|
||||
let buildRefreshTimer = null;
|
||||
const SSE_HANDLERS = {
|
||||
rebuild_queue_changed() {
|
||||
// No `ev.queue` payload read anymore — this event still carries its own
|
||||
// queue snapshot on the wire (tabs.js/SW4RM still consumes it for the
|
||||
// swarm badges, untouched by this), this page just treats it as a
|
||||
// refetch trigger. `JobqGraph` owns the actual fetch.
|
||||
jobqGraphToken += 1;
|
||||
renderRebuildQueue();
|
||||
// Auto-refresh build log list when the queue changes and BUILD L0GS is active.
|
||||
if (buildTabs && buildTabs.active() === "buildlogs") {
|
||||
if (buildRefreshTimer) clearTimeout(buildRefreshTimer);
|
||||
buildRefreshTimer = setTimeout(fetchBuild, 2000);
|
||||
}
|
||||
},
|
||||
meta_inputs_changed(ev) {
|
||||
metaInputsState = (ev.inputs || []).slice();
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
},
|
||||
meta_update_running(ev) {
|
||||
metaUpdateRunning = !!ev.running;
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
},
|
||||
};
|
||||
|
||||
// ─── boot ─────────────────────────────────────────────────────────────────────
|
||||
async function refreshState() {
|
||||
try {
|
||||
const resp = await fetch("/api/state");
|
||||
if (resp.ok) syncFromSnapshot(await resp.json());
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
renderAll();
|
||||
}
|
||||
|
||||
async function init() {
|
||||
initServerWarnings();
|
||||
bindAsyncForms(() => refreshState());
|
||||
// Self-fetches on mount (JobqGraph's own effect) — no explicit initial
|
||||
// fetch needed here, unlike meta inputs below.
|
||||
/** Mount the rebuild queue and wire the build-log refresh button. Called
|
||||
* once, after the tab strip exists. The queue self-fetches on mount
|
||||
* (JobqGraph's own effect), unlike the meta inputs, which wait for the
|
||||
* snapshot. */
|
||||
export function initBuilds() {
|
||||
const refresh = $("build-refresh");
|
||||
if (refresh) refresh.addEventListener("click", fetchBuild);
|
||||
mountRebuildQueue();
|
||||
}
|
||||
|
||||
buildTabs = document.getElementById("builds-tabbar").configure({
|
||||
tabs: [
|
||||
{
|
||||
id: "rebuild",
|
||||
label: "R3BU1LD QU3U3",
|
||||
badgeId: "builds-tab-count-rebuild",
|
||||
},
|
||||
{ id: "meta", label: "M3T4 1NPUTS" },
|
||||
{ id: "buildlogs", label: "BUILD L0GS" },
|
||||
],
|
||||
defaultId: "rebuild",
|
||||
onShow: (id) => {
|
||||
if (id === "buildlogs" && !buildLogsLoaded) {
|
||||
buildLogsLoaded = true;
|
||||
fetchBuild();
|
||||
}
|
||||
},
|
||||
});
|
||||
/** The meta inputs, from an `/api/state` snapshot. */
|
||||
export function syncBuildsFromSnapshot(s) {
|
||||
syncFromSnapshot(s);
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
}
|
||||
|
||||
await refreshState();
|
||||
|
||||
// kinds= matches SSE_HANDLERS below verbatim — narrows this from all
|
||||
// 13 wire kinds down to the 3 this page acts on. This page was one
|
||||
// of 4 unfiltered `/api/dashboard/stream` subscribers before this
|
||||
// (subscription discipline, part 1 of the dashboard-event-stream-
|
||||
// split issue).
|
||||
const es = openStream(
|
||||
"/api/dashboard/stream?kinds=rebuild_queue_changed,meta_inputs_changed,meta_update_running",
|
||||
);
|
||||
if (es) {
|
||||
es.onmessage = (e) => {
|
||||
let ev;
|
||||
try {
|
||||
ev = JSON.parse(e.data);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const h = SSE_HANDLERS[ev.kind];
|
||||
if (h) h(ev);
|
||||
};
|
||||
/** A tab was shown: BUILD L0GS loads its list the first time. */
|
||||
export function onBuildsTabShown(tab) {
|
||||
if (tab === "buildlogs" && !buildLogsLoaded) {
|
||||
buildLogsLoaded = true;
|
||||
fetchBuild();
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
let buildRefreshTimer = null;
|
||||
|
||||
/** `rebuild_queue_changed`: a payload-less refetch trigger — `JobqGraph`
|
||||
* owns the actual fetch. */
|
||||
export function onRebuildQueueChanged() {
|
||||
jobqGraphToken += 1;
|
||||
renderRebuildQueue();
|
||||
// Auto-refresh the build log list while BUILD L0GS is the active tab.
|
||||
if (document.body.dataset.activeTab === "buildlogs") {
|
||||
if (buildRefreshTimer) clearTimeout(buildRefreshTimer);
|
||||
buildRefreshTimer = setTimeout(fetchBuild, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
export function onMetaInputsChanged(ev) {
|
||||
metaInputsState = (ev.inputs || []).slice();
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
}
|
||||
|
||||
export function onMetaUpdateRunning(ev) {
|
||||
metaUpdateRunning = !!ev.running;
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ code {
|
|||
Semantic colour variants only — shape comes from `hive-pill-sm`
|
||||
(shared with the agent UI's pill/chip family, @hive/shared/pill.css),
|
||||
carried directly in the markup alongside each of these classnames
|
||||
(swarm.js/core.js/builds.js). Used on container rows and build-log
|
||||
(agents.js/core.js/builds.js). Used on container rows and build-log
|
||||
rows. */
|
||||
.badge-warn {
|
||||
color: var(--amber);
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ export function openStream(url) {
|
|||
}
|
||||
|
||||
// Stream a build log into a <pre>, returning the EventSource. Used by the
|
||||
// BU1LDS page's BUILD L0GS tab. Appends `stdout_append` then
|
||||
// BUILD L0GS tab. Appends `stdout_append` then
|
||||
// `stderr_append` (one `--- stderr ---` separator) frames from
|
||||
// `GET /api/build-logs/id/{id}/stream`; auto-scrolls to the bottom unless the operator scrolled up; on the terminal `done` frame
|
||||
// closes the stream and calls `onDone(status)`; on a non-transient error
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/* ─── /core.html — C0R3 page ───────────────────────────────────────
|
||||
Kept state (tombstones) and stale permission entries. Section rules
|
||||
imported from system-sections.css (shared with dashboard.css +
|
||||
builds.css). */
|
||||
imported from system-sections.css (shared with dashboard.css). */
|
||||
@import "./system-sections.css";
|
||||
|
||||
body.core-shell {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@
|
|||
<!-- C0R3: the host/coordinator surface. Standalone page (served at
|
||||
/core.html) carved out of the dashboard's old SYST3M tab so the
|
||||
dashboard tab strip stays lean: kept state (tombstones) and the
|
||||
stale permission entries. The section <div> ids (tombstones-section,
|
||||
stale permission entries. Rebuild queue + meta inputs are tabs of
|
||||
/dashboard.html. The section <div> ids (tombstones-section,
|
||||
tombstones-stale-perms) match what core.js's renderers target. Hive
|
||||
infra containers (`hive-forge`, `hive-gateway`, `hive-matrix`) have
|
||||
no dashboard panel — `hivectl stop`/`start`/`restart` is the only
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
// standalone page so the dashboard tab strip stays lean: kept state
|
||||
// (tombstones) and the stale permission entries.
|
||||
//
|
||||
// This page is its own esbuild bundle with its own runtime: it cold-loads
|
||||
// Rebuild queue + meta inputs are tabs of /dashboard.html, not here. This
|
||||
// page is its own esbuild bundle with its own runtime: it cold-loads
|
||||
// /api/state and subscribes to /dashboard/stream (the same broker event
|
||||
// channel the dashboard uses), maintaining its own copy of the tombstones
|
||||
// state.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
pulls them in here. */
|
||||
@import "./system-sections.css";
|
||||
@import "@hive/shared/jobq-rollup.css";
|
||||
@import "@hive/shared/jobq-graph.css";
|
||||
|
||||
/* ─── tabbed dashboard chrome ──────────────────────────────────────
|
||||
Top-of-page sticky header with banner + tab strip. SSE stays
|
||||
|
|
@ -125,10 +126,15 @@ body.dashboard-shell {
|
|||
font-variant-numeric: tabular-nums;
|
||||
font-weight: bold;
|
||||
}
|
||||
/* `display` above beats the UA's `[hidden]` rule, so an empty count (the
|
||||
4G3NTS and R3BU1LD QU3U3 pills at zero) would otherwise draw a "0". */
|
||||
.tab-count[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tab pane visibility — createTabStrip (@hive/shared/tabs.js) sets the
|
||||
`hidden` attribute on every inactive pane (resolved from the URL hash;
|
||||
default — no hash — is SW4RM). Only the active pane stays visible. */
|
||||
default — no hash — is 4G3NTS). Only the active pane stays visible. */
|
||||
.tab-pane[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -316,7 +322,7 @@ hive-agent-menu {
|
|||
--menu-btn-opacity: 1;
|
||||
}
|
||||
/* Card actions dim while a row has any pending-state badge (transient-
|
||||
driven only now — see swarm.js's pending-badge derivation comment;
|
||||
driven only now — see agents.js's pending-badge derivation comment;
|
||||
there is no separate queued-but-not-running row state to tell apart
|
||||
from this one anymore). */
|
||||
.container-row.pending-running .actions {
|
||||
|
|
@ -437,12 +443,12 @@ hive-agent-menu {
|
|||
shared <hive-warn> component — amber/no-pulse for
|
||||
the standing caveat (a pulsing banner that's always present just
|
||||
teaches you to stop seeing it), `severity="red" pulse` for the port
|
||||
collision (an incident, not a caveat). See core.js/swarm.js and
|
||||
collision (an incident, not a caveat). See core.js/agents.js and
|
||||
hive-warn.css for the actual styling now. */
|
||||
|
||||
/* Build-queue summary banner on the SW4RM tab moved to the shared
|
||||
/* Build-queue summary banner on the 4G3NTS tab moved to the shared
|
||||
`JobqRollup` Preact component (`.jqr-*` classes, imported above) —
|
||||
mounted by swarm.js::initJobqRollup into #jobq-rollup-section. */
|
||||
mounted by agents.js::initJobqRollup into #jobq-rollup-section. */
|
||||
|
||||
.pending-state {
|
||||
color: var(--amber);
|
||||
|
|
@ -772,3 +778,120 @@ body.dashboard-shell.has-selection {
|
|||
/* ST4TS moved to its own page (`/stats.html`): the
|
||||
window selector / summary chips / bars live in stats.css, and the
|
||||
shared `.hive-stats-table` moved to common.css. */
|
||||
|
||||
/* ─── build tabs (builds.js) ────────────────────────────────────────
|
||||
R3BU1LD QU3U3 and M3T4 1NPUTS are styled by system-sections.css and
|
||||
jobq-graph.css; the BUILD L0GS list rules are below. */
|
||||
|
||||
.logs-toolbar {
|
||||
display: flex;
|
||||
gap: 0.6em;
|
||||
align-items: center;
|
||||
margin-bottom: 0.9em;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ─── build-log list ──────────────────────────────────────────────
|
||||
Scrollable list of log header rows; clicking a row expands full
|
||||
stdout+stderr inline. */
|
||||
.build-logs-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
}
|
||||
.build-logs-item {
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0.15em 0;
|
||||
}
|
||||
.build-logs-row-btn {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4em;
|
||||
align-items: baseline;
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--fg);
|
||||
font-family: inherit;
|
||||
font-size: 0.85em;
|
||||
text-align: left;
|
||||
padding: 0.25em 0.4em;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.build-logs-row-btn:hover {
|
||||
background: var(--bg-elev);
|
||||
}
|
||||
.build-logs-kind {
|
||||
font-weight: 600;
|
||||
}
|
||||
.build-logs-age {
|
||||
font-size: 0.88em;
|
||||
}
|
||||
.build-logs-cmdline {
|
||||
color: var(--muted);
|
||||
font-size: 0.82em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 40em;
|
||||
}
|
||||
.build-logs-detail {
|
||||
padding: 0 0.4em 0.4em;
|
||||
}
|
||||
.build-logs-output {
|
||||
margin: 0.3em 0 0;
|
||||
padding: 0.4em 0.6em;
|
||||
background: var(--crust);
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--purple-dim);
|
||||
font-size: 0.82em;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow-x: auto;
|
||||
max-height: 30em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.build-logs-dl {
|
||||
color: var(--muted);
|
||||
font-size: 0.8em;
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--purple-dim);
|
||||
padding: 0.1em 0.4em;
|
||||
border-radius: 2px;
|
||||
margin-top: 0.3em;
|
||||
display: none;
|
||||
}
|
||||
.build-logs-dl:not([hidden]) {
|
||||
display: inline-block;
|
||||
}
|
||||
.build-logs-dl:hover {
|
||||
color: var(--fg);
|
||||
border-color: var(--purple-dim);
|
||||
}
|
||||
|
||||
.build-logs-live-badge {
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
|
||||
@keyframes live-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.45;
|
||||
}
|
||||
}
|
||||
.build-logs-live-badge.badge-running {
|
||||
animation: live-pulse 1.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.build-logs-runtime {
|
||||
font-size: 0.85em;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,20 +26,51 @@
|
|||
<nav class="tabbar" id="tabbar" role="tablist">
|
||||
<a
|
||||
class="tab"
|
||||
id="tab-swarm"
|
||||
href="#swarm"
|
||||
id="tab-agents"
|
||||
href="#agents"
|
||||
role="tab"
|
||||
aria-controls="tab-pane-swarm"
|
||||
data-tab="swarm"
|
||||
aria-controls="tab-pane-agents"
|
||||
data-tab="agents"
|
||||
>
|
||||
<span class="tab-label">◆ SW4RM ◆</span>
|
||||
<span class="tab-count" id="tab-count-swarm" hidden></span>
|
||||
<span class="tab-label">◆ 4G3NTS ◆</span>
|
||||
<span class="tab-count" id="tab-count-agents" hidden></span>
|
||||
</a>
|
||||
<!-- SYST3M moved to its own standalone page (/core.html, "C0R3"),
|
||||
reached from the H0M3 hub — keeps the dashboard tab strip lean.
|
||||
The rebuild-queue STATE still lives in tabs.js (it drives the
|
||||
"building…" badges on SW4RM agent cards); only the SYST3M tab +
|
||||
its section renderers moved out. -->
|
||||
The build lifecycle is three tabs here (builds.js): the rebuild
|
||||
queue with its live log, the meta flake inputs, and the build log
|
||||
history. -->
|
||||
<a
|
||||
class="tab"
|
||||
id="tab-rebuild"
|
||||
href="#rebuild"
|
||||
role="tab"
|
||||
aria-controls="tab-pane-rebuild"
|
||||
data-tab="rebuild"
|
||||
>
|
||||
<span class="tab-label">◆ R3BU1LD QU3U3 ◆</span>
|
||||
<span class="tab-count" id="tab-count-rebuild" hidden></span>
|
||||
</a>
|
||||
<a
|
||||
class="tab"
|
||||
id="tab-meta"
|
||||
href="#meta"
|
||||
role="tab"
|
||||
aria-controls="tab-pane-meta"
|
||||
data-tab="meta"
|
||||
>
|
||||
<span class="tab-label">◆ M3T4 1NPUTS ◆</span>
|
||||
</a>
|
||||
<a
|
||||
class="tab"
|
||||
id="tab-buildlogs"
|
||||
href="#buildlogs"
|
||||
role="tab"
|
||||
aria-controls="tab-pane-buildlogs"
|
||||
data-tab="buildlogs"
|
||||
>
|
||||
<span class="tab-label">◆ BUILD L0GS ◆</span>
|
||||
</a>
|
||||
<!-- P3RM1SS10NS: per-agent capability grants and tool-group
|
||||
assignments. Both tables are fetched on tab activation. -->
|
||||
<a
|
||||
|
|
@ -58,7 +89,7 @@
|
|||
view, not part of the operational tab strip. -->
|
||||
|
||||
<!-- (Peer hives are no longer a tab — they render as a headline
|
||||
under SW4RM. See tab-pane-swarm.) -->
|
||||
under 4G3NTS. See tab-pane-agents.) -->
|
||||
|
||||
<!-- FL0W and the other standalone pages are reachable from the
|
||||
H0M3 hub (served at /), not from the dashboard tab strip —
|
||||
|
|
@ -68,33 +99,33 @@
|
|||
|
||||
<!-- Tab panes. createTabStrip (@hive/shared/tabs.js) keeps exactly one
|
||||
visible by toggling the `hidden` attribute, resolved from the URL
|
||||
hash (default SW4RM). Panes start `hidden` to avoid a flash before
|
||||
hash (default 4G3NTS). Panes start `hidden` to avoid a flash before
|
||||
the script runs. -->
|
||||
<!-- Padded content wrapper: carries the 1.5em side gutter
|
||||
(.page-content, common.css) while <body> stays full-bleed so the
|
||||
sticky chrome above + the footer below span the full width. -->
|
||||
<div class="page-content">
|
||||
<main class="dashboard-main">
|
||||
<!-- SW4RM: the swarm itself. Container cards (the central thing
|
||||
<!-- 4G3NTS: the agents. Container cards (the central thing
|
||||
the operator looks at) and rebuild queue / cascade visualisation
|
||||
that drives them. No inline `C0NTAINERS` h2 heading + divider
|
||||
— the tab label SW4RM already says it. -->
|
||||
— the tab label 4G3NTS already says it. -->
|
||||
<section
|
||||
class="tab-pane"
|
||||
id="tab-pane-swarm"
|
||||
data-tab-pane="swarm"
|
||||
id="tab-pane-agents"
|
||||
data-tab-pane="agents"
|
||||
hidden
|
||||
role="tabpanel"
|
||||
aria-labelledby="tab-swarm"
|
||||
aria-labelledby="tab-agents"
|
||||
>
|
||||
<!-- Swarm / hive identity headline. Populated by refreshState from
|
||||
hive_name + swarm_name; stays hidden when neither is set. -->
|
||||
<h2 id="swarm-identity" hidden></h2>
|
||||
<!-- JobqRollup mount point, kept outside #containers-section
|
||||
deliberately — that section is wiped + rebuilt on every
|
||||
container-list render (see swarm.js::renderContainers), which
|
||||
container-list render (see agents.js::renderContainers), which
|
||||
would tear down and remount the Preact tree on every
|
||||
container-state tick. Mounted once by swarm.js::initJobqRollup,
|
||||
container-state tick. Mounted once by agents.js::initJobqRollup,
|
||||
refreshed via its own handle rather than by re-rendering. -->
|
||||
<div id="jobq-rollup-section"></div>
|
||||
<div id="containers-section">
|
||||
|
|
@ -102,9 +133,77 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SYST3M pane moved to /core.html (the standalone "C0R3" page):
|
||||
kept state and stale permissions. Reached
|
||||
from the H0M3 hub. -->
|
||||
<!-- R3BU1LD QU3U3: pending + running rebuilds, meta-updates, and
|
||||
first-spawns. Rendered from GET /api/jobq/graph by the
|
||||
`JobqGraph` Preact component (@hive/shared/jobq-graph.js);
|
||||
`rebuild_queue_changed` over /api/dashboard/stream is the
|
||||
refresh trigger and carries no payload of its own. -->
|
||||
<section
|
||||
class="tab-pane"
|
||||
id="tab-pane-rebuild"
|
||||
data-tab-pane="rebuild"
|
||||
hidden
|
||||
role="tabpanel"
|
||||
aria-labelledby="tab-rebuild"
|
||||
>
|
||||
<p class="meta">
|
||||
pending + running rebuilds, meta-updates, and first-spawns. one runs
|
||||
at a time; meta-update cascades nest under their parent. dedup:
|
||||
re-enqueueing a still-queued op collapses into the existing entry.
|
||||
</p>
|
||||
<div id="rebuild-queue-section">
|
||||
<p class="meta">loading…</p>
|
||||
</div>
|
||||
<!-- Live build log of the currently-running rebuild (one runs at a
|
||||
time). Managed by renderRebuildLiveLog in builds.js, separate from
|
||||
rebuild-queue-section so the queue's row re-render never disturbs
|
||||
the open poll. -->
|
||||
<div id="rebuild-live-log" class="rebuild-live-log" hidden></div>
|
||||
</section>
|
||||
|
||||
<!-- M3T4 1NPUTS: select inputs to nix flake update in /meta/. -->
|
||||
<section
|
||||
class="tab-pane"
|
||||
id="tab-pane-meta"
|
||||
data-tab-pane="meta"
|
||||
hidden
|
||||
role="tabpanel"
|
||||
aria-labelledby="tab-meta"
|
||||
>
|
||||
<p class="meta">
|
||||
select inputs to <code>nix flake update</code> in
|
||||
<code>/meta/</code>. selected agents rebuild in sequence after the
|
||||
lock bump; the submitting agent learns each outcome via the usual
|
||||
<code>rebuilt</code> system event.
|
||||
</p>
|
||||
<div id="meta-inputs-section">
|
||||
<p class="meta">loading…</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- BUILD L0GS: all-agents build log history. Click a row to expand
|
||||
stdout + stderr. Live builds stream in real time. Lazy-loaded the
|
||||
first time the tab is shown; auto-refreshes when
|
||||
rebuild_queue_changed fires. Deep-link: ?id=N#buildlogs. -->
|
||||
<section
|
||||
class="tab-pane"
|
||||
id="tab-pane-buildlogs"
|
||||
data-tab-pane="buildlogs"
|
||||
hidden
|
||||
role="tabpanel"
|
||||
aria-labelledby="tab-buildlogs"
|
||||
>
|
||||
<p class="meta">
|
||||
all-agents build log history. click a row to expand stdout + stderr.
|
||||
live builds stream in real time.
|
||||
</p>
|
||||
<div class="logs-toolbar">
|
||||
<button type="button" class="btn btn-restart" id="build-refresh">
|
||||
↻ refresh
|
||||
</button>
|
||||
</div>
|
||||
<div id="build-list"><p class="meta">loading…</p></div>
|
||||
</section>
|
||||
|
||||
<!-- P3RM1SS10NS: per-agent capability grants + tool-group
|
||||
assignments. Both tables are column-driven from the backend
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
<span class="home-tile-label">Dashboard</span>
|
||||
</span>
|
||||
<span class="home-tile-desc"
|
||||
>containers · permissions · system</span
|
||||
>containers · builds · permissions</span
|
||||
>
|
||||
</a>
|
||||
|
||||
|
|
@ -54,16 +54,6 @@
|
|||
<span class="home-tile-desc">live all-agents message firehose</span>
|
||||
</a>
|
||||
|
||||
<a class="home-tile" href="/builds.html">
|
||||
<span class="home-tile-head">
|
||||
<span class="home-tile-icon" aria-hidden="true">🔨</span>
|
||||
<span class="home-tile-label">Builds</span>
|
||||
</span>
|
||||
<span class="home-tile-desc"
|
||||
>rebuild queue · meta inputs · build logs</span
|
||||
>
|
||||
</a>
|
||||
|
||||
<a class="home-tile" href="/stats.html">
|
||||
<span class="home-tile-head">
|
||||
<span class="home-tile-icon" aria-hidden="true">📊</span>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/* ─── SYST3M/BU1LDS section panels — shared by dashboard, /core.html,
|
||||
and /builds.html ──────────────────────────────────────────────────
|
||||
/* ─── SYST3M/BU1LDS section panels — shared by dashboard and /core.html ───
|
||||
Rebuild queue, meta inputs and kept-state panels.
|
||||
Imported by core.css, dashboard.css, and builds.css. */
|
||||
Imported by core.css and dashboard.css. */
|
||||
|
||||
/* ─── meta inputs panel ────────────────────────────────────────────── */
|
||||
.meta-inputs {
|
||||
|
|
@ -124,9 +123,8 @@
|
|||
}
|
||||
/* ─── running-rebuild live log (renderRebuildLiveLog in builds.js) ──────────
|
||||
Streams the currently-running rebuild's build log inline under the queue.
|
||||
Hidden (native `hidden` attr) when nothing is building. Used on
|
||||
/builds.html; lives here (not in builds.css) so it can be shared if
|
||||
the dashboard ever gains an inline live-log view. */
|
||||
Hidden (native `hidden` attr) when nothing is building. Used on the
|
||||
dashboard's R3BU1LD QU3U3 tab. */
|
||||
.rebuild-live-log {
|
||||
margin-top: 0.8em;
|
||||
border: 1px solid var(--purple-dim);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
// 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.
|
||||
// SW4RM (containers) domain lives in `./swarm.js`.
|
||||
// 4G3NTS (containers) domain lives in `./agents.js`.
|
||||
|
||||
import { marked } from "marked";
|
||||
import { $, openStream, renderServerWarnings } from "./common.js";
|
||||
|
|
@ -33,7 +33,16 @@ import {
|
|||
applyTransientCleared,
|
||||
renderContainers,
|
||||
renderSelectionBar,
|
||||
} from "./swarm.js";
|
||||
} from "./agents.js";
|
||||
import {
|
||||
initBuilds,
|
||||
onBuildsTabShown,
|
||||
onMetaInputsChanged,
|
||||
onMetaUpdateRunning,
|
||||
onRebuildQueueChanged,
|
||||
rebuildCount,
|
||||
syncBuildsFromSnapshot,
|
||||
} from "./builds.js";
|
||||
|
||||
// mdNode (in common.js) reads `window.marked` for the markdown side
|
||||
// panel preview path. Set it here on the dashboard entry so file
|
||||
|
|
@ -117,7 +126,7 @@ window.marked = marked;
|
|||
window.__hyperhive_state = s;
|
||||
renderServerWarnings(s.server_warnings);
|
||||
// Hive identity: render the swarm/hive name as a headline at the
|
||||
// top of the SW4RM pane + update the page title once the
|
||||
// top of the 4G3NTS pane + update the page title once the
|
||||
// server-side display names are known. `hive_name` / `swarm_name`
|
||||
// come from HYPERHIVE_HIVE_NAME / HYPERHIVE_SWARM_NAME env vars
|
||||
// (set by services.hyperhive.{hiveName,swarmName} nix options).
|
||||
|
|
@ -139,13 +148,14 @@ window.marked = marked;
|
|||
// `transientsState` + `containersState`, not from `s.*`).
|
||||
syncTransientsFromSnapshot(s);
|
||||
syncContainersFromSnapshot(s);
|
||||
// Job-queue rollup feeds only the SW4RM queue-summary banner
|
||||
// (per-agent card badges are transient-only — see swarm.js). Its
|
||||
// Job-queue rollup feeds only the 4G3NTS queue-summary banner
|
||||
// (per-agent card badges are transient-only — see agents.js). Its
|
||||
// own `JobqRollup` mount self-fetches GET /api/jobq/rollup — not
|
||||
// read off `s` (this page's snapshot carries no jobq field).
|
||||
// initJobqRollup mounts once and is a no-op on later calls; the
|
||||
// mount's own effect handles the actual fetch.
|
||||
initJobqRollup();
|
||||
syncBuildsFromSnapshot(s);
|
||||
renderContainers(s);
|
||||
restoreOpenDetails(openDetails);
|
||||
// No periodic refresh timer. Phase 6 covers every container
|
||||
|
|
@ -172,7 +182,7 @@ window.marked = marked;
|
|||
|
||||
// ─── live updates: dashboard event stream ──────────────────────────────
|
||||
// The dashboard subscribes to /dashboard/stream for live mutation
|
||||
// events so the SW4RM / SYST3M panes update without an
|
||||
// events so the 4G3NTS / SYST3M panes update without an
|
||||
// operator action triggering a refreshState.
|
||||
//
|
||||
// Bare `EventSource` (no terminal infrastructure needed — the
|
||||
|
|
@ -191,11 +201,15 @@ window.marked = marked;
|
|||
transient_cleared: applyTransientCleared,
|
||||
container_state_changed: applyContainerStateChanged,
|
||||
container_removed: applyContainerRemoved,
|
||||
// rebuild_queue_changed: refreshes the SW4RM queue-summary banner
|
||||
// (see swarm.js) — a payload-less push trigger, same treatment
|
||||
// /builds.html gives it for its JobqGraph mount handle's .refresh()
|
||||
// (its own separate subscription).
|
||||
rebuild_queue_changed: applyRebuildQueueChanged,
|
||||
// rebuild_queue_changed: a payload-less push trigger. Refreshes the
|
||||
// 4G3NTS queue-summary banner (see agents.js) and the R3BU1LD QU3U3
|
||||
// tab's JobqGraph (see builds.js).
|
||||
rebuild_queue_changed: () => {
|
||||
applyRebuildQueueChanged();
|
||||
onRebuildQueueChanged();
|
||||
},
|
||||
meta_inputs_changed: onMetaInputsChanged,
|
||||
meta_update_running: onMetaUpdateRunning,
|
||||
capabilities_changed: applyCapabilitiesChanged,
|
||||
tool_groups_changed: applyToolGroupsChanged,
|
||||
};
|
||||
|
|
@ -208,11 +222,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 9 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," +
|
||||
"capabilities_changed,tool_groups_changed",
|
||||
);
|
||||
es.onmessage = (e) => {
|
||||
|
|
@ -245,9 +260,9 @@ window.marked = marked;
|
|||
})();
|
||||
|
||||
// ─── tab routing ───────────────────────────────────────────────────────
|
||||
// Hash-based: `#swarm` / `#system` / `#permissions` activate the
|
||||
// matching pane on the dashboard. Empty
|
||||
// hash defaults to SW4RM. FL0W and ST4TS are NOT tabs —
|
||||
// Hash-based: `#agents` / `#rebuild` / `#meta` / `#buildlogs` /
|
||||
// `#permissions` 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
|
||||
// applies when the tab DOM is present (e.g. not on the flow page
|
||||
|
|
@ -260,7 +275,7 @@ window.marked = marked;
|
|||
// to their own pages rather than hide them in a dropdown.
|
||||
function activateTab(target) {
|
||||
// Track active tab on the body so renderSelectionBar can gate
|
||||
// visibility (bar only belongs on SW4RM where agent cards live).
|
||||
// visibility (bar only belongs on 4G3NTS where agent cards live).
|
||||
document.body.dataset.activeTab = target;
|
||||
renderSelectionBar(Array.from(containersState.values()));
|
||||
// Permissions tables: SSE covers worker-applied changes
|
||||
|
|
@ -272,13 +287,17 @@ window.marked = marked;
|
|||
fetchAndRenderCapabilities();
|
||||
fetchAndRenderToolGroups();
|
||||
}
|
||||
onBuildsTabShown(target);
|
||||
}
|
||||
|
||||
// Wire the shared tab strip now that activateTab + the lazy-load fns it
|
||||
// calls are defined. The strip resolves the active tab from the hash
|
||||
// (default SW4RM), toggles the active tab/pane + aria-selected, and
|
||||
// (default 4G3NTS), toggles the active tab/pane + aria-selected, and
|
||||
// fires activateTab for the per-tab side-effects on every change.
|
||||
createTabStrip($("tabbar"), { defaultId: "swarm", onShow: activateTab });
|
||||
createTabStrip($("tabbar"), { defaultId: "agents", onShow: activateTab });
|
||||
// The rebuild queue mounts now, not on first show, so its tab's count pill
|
||||
// is live from the start.
|
||||
initBuilds();
|
||||
|
||||
// Tab count pills — pure derived data from the existing state
|
||||
// stores so SSE-driven updates flow through without extra plumbing.
|
||||
|
|
@ -294,14 +313,16 @@ window.marked = marked;
|
|||
/** Recompute every tab's count from the current state. Called on
|
||||
* every renderXxx that's tab-relevant. */
|
||||
function refreshTabCounts() {
|
||||
// SW4RM — flag any container that's stale (needs_update). Empty
|
||||
// 4G3NTS — flag any container that's stale (needs_update). Empty
|
||||
// when everyone's current. Container-row pulse signals state
|
||||
// transitions; the pill catches "deploy-pending" specifically.
|
||||
let swarm = 0;
|
||||
let stale = 0;
|
||||
for (const c of containersState.values()) {
|
||||
if (c.needs_update) swarm++;
|
||||
if (c.needs_update) stale++;
|
||||
}
|
||||
setTabCount("swarm", swarm);
|
||||
setTabCount("agents", stale);
|
||||
// R3BU1LD QU3U3 — rebuilds in flight.
|
||||
setTabCount("rebuild", rebuildCount());
|
||||
}
|
||||
// Poll the state stores on a 1s tick to keep the pill counts in
|
||||
// sync. The state stores are mutated synchronously by every SSE
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/* ─── page chrome: back-link header ────────────────────────────────
|
||||
The minimal sticky "frosted bar" header used by every standalone page
|
||||
reached from the H0M3 hub — /flow, /stats, /core, /builds. A back
|
||||
link to H0M3 on the left, then either a page title or (on /core and
|
||||
/builds) a sub-tab nav. The operator dashboard uses its own richer
|
||||
reached from the H0M3 hub — /flow, /stats, /core. A back
|
||||
link to H0M3 on the left, then either a page title or (on /core) a
|
||||
sub-tab nav. The operator dashboard uses its own richer
|
||||
sticky chrome (`.dashboard-chrome`); these classes cover the simple
|
||||
pages.
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
// widening from the old per-agent-menu-only coordination) so
|
||||
// `closeAll()` can close each via its own `.close()`. `closeAllMenus()`
|
||||
// force-closes everything before tearing down DOM one might anchor off
|
||||
// of (e.g. swarm.js before replacing the container tree).
|
||||
// of (e.g. agents.js before replacing the container tree).
|
||||
//
|
||||
// The document-level listeners below, registered once: shadow-DOM
|
||||
// retargeting means `e.target` for a click inside a shadow tree gets
|
||||
|
|
@ -58,7 +58,7 @@ document.addEventListener(
|
|||
true,
|
||||
);
|
||||
// Close on Escape. stopImmediatePropagation so a caller's own
|
||||
// selection-clear Escape handler (e.g. swarm.js's) doesn't also fire
|
||||
// selection-clear Escape handler (e.g. agents.js's) doesn't also fire
|
||||
// while a menu is open.
|
||||
document.addEventListener(
|
||||
"keydown",
|
||||
|
|
@ -76,7 +76,7 @@ class HiveMenu extends HTMLElement {
|
|||
// A custom element's connectedCallback fires again on a same-document
|
||||
// *move* (insertBefore/append repositioning an already-connected node
|
||||
// runs the removal + insertion steps for its whole subtree), not just
|
||||
// on a fresh mount — swarm.js's row-fingerprint cache reuses/reorders
|
||||
// on a fresh mount — agents.js's row-fingerprint cache reuses/reorders
|
||||
// existing <li> subtrees, which can move an already-initialised
|
||||
// <hive-menu> without ever detaching it from the document. Guard
|
||||
// against re-running setup (attachShadow throws if the host already
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// hive-warn.js — <hive-warn>, the shared inline warning-banner
|
||||
// component. Consolidates what were independently-written instances of
|
||||
// the same thing, among them `.tombstone-warn` (core.js, JS-built) and
|
||||
// `.port-conflict` (swarm.js, JS-built) — some differed only by an
|
||||
// `.port-conflict` (agents.js, JS-built) — some differed only by an
|
||||
// undeliberate 10% vs 8% tint, the strongest evidence this was drift,
|
||||
// not genuinely different needs.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// JobqRollup.tsx — <JobqRollup>, a compact one-line banner over
|
||||
// GET .../api/jobq/rollup's pre-tallied `Vec<hive_jobq_wire::StateCount>`
|
||||
// (any endpoint serving that shape works — hive-c0re and swarm-controller
|
||||
// both do). Extracted out of dashboard/src/swarm.js's hand-rolled
|
||||
// both do). Extracted out of dashboard/src/agents.js's hand-rolled
|
||||
// queue-summary banner, which this component now replaces there too —
|
||||
// same "one shared component, not two parallel implementations" pattern
|
||||
// `JobqGraph` already established for the rebuild-queue tree view.
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
// worth a line of chrome).
|
||||
//
|
||||
// `queueHref`, when given, adds a "view queue →" link (dashboard links to
|
||||
// /builds.html; swarm-ui's /jobs page omits it — a link to the page
|
||||
// the dashboard's #rebuild tab; swarm-ui's /jobs page omits it — a link to the page
|
||||
// you're already on is noise).
|
||||
//
|
||||
// The glyph carries `.spinner` (`@hive/shared/base.css`, imported by
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
//
|
||||
// JSX (swarm-ui) — `<JobqRollup endpoint="..." queueHref="..." />`. Or
|
||||
// plain `render(h(JobqRollup, props), container)` (dashboard/src/
|
||||
// swarm.js, no JSX pragma needed) — call again with a bumped
|
||||
// agents.js, no JSX pragma needed) — call again with a bumped
|
||||
// `refreshToken` to force a refetch. No mount wrapper: `render` is
|
||||
// already the re-render/diff entry point.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// hive-tab-strip.js — <hive-tab-strip>, the markup-owning tab-strip custom
|
||||
// element behind the logs/core/builds sub-page tabbars. Owns
|
||||
// element behind the logs/core sub-page tabbars. Owns
|
||||
// rendering the <a class="hive-tab"> markup from a declarative `tabs` list
|
||||
// instead of every page hand-writing the same <nav><a>...</a></nav>
|
||||
// boilerplate, then wires the existing createTabStrip() behaviour
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
// — same id/aria-controls scheme the hand-written markup used, so each
|
||||
// page's panes (aria-labelledby="${prefix}-tab-${id}") don't need to
|
||||
// change. A `badgeId` tab also gets a nested `<span class="tab-count"
|
||||
// id="${badgeId}" hidden>` (matches builds.html's rebuild-queue count
|
||||
// id="${badgeId}" hidden>` (e.g. a rebuild-queue count
|
||||
// pill), then wires up createTabStrip() and returns exactly what it
|
||||
// returns ({ show, active }) — every existing `.active()`/`.show(id)`
|
||||
// call site keeps working unchanged. Call configure() exactly once per
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
// swarm-level job (e.g. CreateAgent) lands.
|
||||
//
|
||||
// JobqRollup sits above the graph, same "N running / M queued" banner
|
||||
// the dashboard's SW4RM tab shows — no `queueHref`, since a "view
|
||||
// the dashboard's 4G3NTS tab shows — no `queueHref`, since a "view
|
||||
// queue →" link back to this same page would be noise.
|
||||
//
|
||||
// Auto-refresh reuses `HivesPage`'s exact pattern (`RefreshIntervalPicker`
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use super::AppState;
|
|||
/// option) when present, otherwise **404** — there is no bundled
|
||||
/// server-side default any more (that was `hive_sh4re::assets::
|
||||
/// branding_svg`, now removed). Consumers fall back client-side: the
|
||||
/// dashboard's `swarm.js` and this agent's own `app.js` both swap an
|
||||
/// dashboard's `agents.js` and this agent's own `app.js` both swap an
|
||||
/// `/icon` load failure to the frontend-bundled `/favicon.svg` rather
|
||||
/// than probing first, so a 404 here is the expected "unconfigured"
|
||||
/// signal, not an error case to work around.
|
||||
|
|
|
|||
Loading…
Reference in a new issue