hyperhive/frontend/packages/dashboard/src/dashboard.html
iris d9c66de069 refactor(frontend): move S3TT1NGS to its own /settings.html page (#1464 step 2)
Extracts the operator-local preferences (browser-notification toggle)
out of the dashboard tab strip into a standalone /settings.html page,
reached from the H0M3 hub — same minimal-chrome pattern as /flow.html
and /logs.html. The dashboard tab strip is now purely operational.

- new settings.{html,css,js}; settings.js binds NOTIF + renders the
  shared server-warnings banner (matching the other standalone pages).
- drop the S3TT1NGS tab + pane from dashboard.html; the NOTIF.bind()
  toggle wiring moves to settings.js. The dashboard keeps NOTIF.show()
  (approvals / questions) — it reads the same browser permission +
  localStorage mute flag the settings page sets, so firing still works.
- move .notif-row / .btn-notif from dashboard.css to settings.css.
- add a Settings tile to the H0M3 hub; wire the new entries into
  build.mjs and update the nix/frontend.nix asset manifest comment.

First slice of #1464 step 2; the ST4TS page extraction follows separately.
2026-06-08 23:44:22 +02:00

330 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>hyperhive // h1ve-c0re</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/dashboard.css">
</head>
<body class="dashboard-shell">
<!-- Sticky chrome — just the tab strip. The "WE ARE THE WIRED"
slug lives at the page footer below `<main>`; chrome is
navigation only. Tabs route via the URL hash so F5 / back-
button / shared links keep you on the same view. JS owns
the show/hide. -->
<header class="dashboard-chrome">
<!-- Back to the H0M3 menu hub (served at /). Sub-pages and the
dashboard all link back to the landing page rather than to each
other — H0M3 is the single navigation hub. -->
<a class="dash-home-back" href="/" title="back to the H0M3 menu">← home</a>
<nav class="tabbar" id="tabbar" role="tablist">
<a class="tab" id="tab-swarm" href="#swarm" role="tab"
aria-controls="tab-pane-swarm"
data-tab="swarm">
<span class="tab-label">◆ SW4RM ◆</span>
<span class="tab-count" id="tab-count-swarm" hidden></span>
</a>
<a class="tab" id="tab-call" href="#call" role="tab"
aria-controls="tab-pane-call"
data-tab="call">
<span class="tab-label">◆ Y3R C4LL ◆</span>
<span class="tab-count tab-count-attn" id="tab-count-call" hidden></span>
</a>
<a class="tab" id="tab-system" href="#system" role="tab"
aria-controls="tab-pane-system"
data-tab="system">
<span class="tab-label">◆ SYST3M ◆</span>
<span class="tab-count" id="tab-count-system" hidden></span>
</a>
<!-- P3RM1SS10NS: per-agent capability grants and tool-group
assignments. Both tables are fetched on tab activation. -->
<a class="tab" id="tab-permissions" href="#permissions" role="tab"
aria-controls="tab-pane-permissions"
data-tab="permissions">
<span class="tab-label">◆ P3RM1SS10NS ◆</span>
</a>
<!-- SCH3DUL3S: scheduled-prompts surface. List of queued
schedules + an operator-direct creation form. Count pill
mirrors the active (non-cancelled) schedule count; hidden
when zero. -->
<a class="tab" id="tab-schedules" href="#schedules" role="tab"
aria-controls="tab-pane-schedules"
data-tab="schedules">
<span class="tab-label">◆ SCH3DUL3S ◆</span>
<span class="tab-count" id="tab-count-schedules" hidden></span>
</a>
<!-- ST4TS: hive-wide turn-stats rollup (swarm totals, busiest
agents, model mix, est cost). Fetched on tab activation +
window change from GET /api/stats-hive. -->
<a class="tab" id="tab-stats" href="#stats" role="tab"
aria-controls="tab-pane-stats"
data-tab="stats">
<span class="tab-label">◆ ST4TS ◆</span>
</a>
<!-- (Peer hives are no longer a tab — they render as a headline
under SW4RM. See tab-pane-swarm.) -->
<!-- FL0W, L0GS, and M4TR1X are separate pages reachable from the
H0M3 hub (served at /), not from the dashboard tab strip — the
strip holds only real in-page tabs now. Contextual deep-links
into the log viewer (an agent's logs, a build entry's log)
still live inside the relevant dashboard content. -->
<!-- S3TT1NGS lives on its own page now (`/settings.html`), reached
from the H0M3 hub — operator-local prefs don't need a slot in
the operational tab strip. -->
</nav>
</header>
<!-- 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
the script runs. -->
<main class="dashboard-main">
<!-- SW4RM: the swarm itself. 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. -->
<section class="tab-pane" id="tab-pane-swarm" data-tab-pane="swarm" hidden
role="tabpanel" aria-labelledby="tab-swarm">
<!-- Swarm / hive identity headline. Populated by refreshState from
hive_name + swarm_name; stays hidden when neither is set. -->
<h2 id="swarm-identity" hidden></h2>
<div id="containers-section">
<p class="meta">loading…</p>
</div>
<!-- Peer hives (federated swarms). The headline block is hidden
entirely when nothing is federated; renderPeerHives then drops
a quiet "no peer hives configured" note into #peers-section. -->
<div id="peers-block" hidden>
<h2>◆ P33R H1V3S ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">peer hives in this swarm. click a card to open that hive's dashboard.</p>
</div>
<div id="peers-section"></div>
</section>
<!-- Y3R C4LL: things blocked on operator decision. Approvals +
questions read as the same concept ("something is waiting on
you"); both surface their full bodies inline so the operator
can decide without leaving the pane. -->
<section class="tab-pane" id="tab-pane-call" data-tab-pane="call" hidden
role="tabpanel" aria-labelledby="tab-call">
<!-- 1NB0X: unread agent→operator messages (#1469). Fetched on
tab activation + cold load, appended live from the broker
stream, cleared via "mark all read". Count folds into the
Y3R C4LL pill so messages aren't missed. -->
<h2>◆ 1NB0X ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<div id="operator-inbox-section">
<p class="meta">loading…</p>
</div>
<h2>◆ P3NDING APPR0VALS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<div id="approvals-section">
<p class="meta">loading…</p>
</div>
<h2>◆ M1ND H4S QU3STI0NS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<div id="questions-section">
<p class="meta">loading…</p>
</div>
</section>
<!-- SYST3M: passive / rare-interaction state. Meta inputs (lock
bumps), rebuild queue (watch only), kept state from previous
tombstoned agents. Per-section content auto-compresses to a
one-line summary when empty (JS toggle). -->
<section class="tab-pane" id="tab-pane-system" data-tab-pane="system" hidden
role="tabpanel" aria-labelledby="tab-system">
<h2>◆ M3T4 1NPUTS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">select inputs to <code>nix flake update</code> in <code>/meta/</code>. selected agents rebuild in sequence after the lock bump; manager learns each outcome via the usual <code>rebuilt</code> system event.</p>
<div id="meta-inputs-section">
<p class="meta">loading…</p>
</div>
<h2>◆ R3BU1LD QU3U3 ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<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>
<h2>◆ K3PT ST4T3 ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<div id="tombstones-section">
<p class="meta">loading…</p>
</div>
<!-- C0NT41N3R L04D: live cpu + memory per agent container, read
from cgroup v2 on the host. Polled only while this tab is
active (cpu needs a short two-sample read each refresh). -->
<h2>◆ C0NT41N3R L04D ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">live cpu + memory per agent container, from cgroup v2 on the host. cpu is % of total host capacity (all cores), sampled over ~200ms each refresh; polled every 5s while this tab is open. network is omitted on purpose — agents share the host netns, so there is no per-container counter (see <code>docs/network.md</code>).</p>
<div id="container-load-section">
<p class="meta">loading…</p>
</div>
</section>
<!-- P3RM1SS10NS: per-agent capability grants + tool-group
assignments. Both tables are column-driven from the backend
(GET /api/capabilities, GET /api/tool-groups) so new entries
require no UI change. Saving POSTs to the respective
/{agent} endpoint and queues a rebuild. -->
<section class="tab-pane" id="tab-pane-permissions" data-tab-pane="permissions" hidden
role="tabpanel" aria-labelledby="tab-permissions">
<h2>◆ C4P4B1L1T13S ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">per-agent capability grants. capabilities unlock gated MCP tools and system access. saving queues a rebuild for the affected agent.</p>
<div id="capabilities-section">
<p class="meta">loading…</p>
</div>
<!-- T00L GR0UPS: per-agent tool-group permission matrix. Rows = agents,
cols = tool groups fetched from GET /api/tool-groups.
Checking / unchecking and saving POSTs to
/api/tool-groups/{agent}; a rebuild is queued automatically.
Absent agents default to the role default (shown in parens). -->
<h2>◆ T00L GR0UPS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">per-agent tool-group permissions. columns are filled from the backend — adding a new group requires no UI change. agents without an explicit entry use the role default (agents: messaging, meta, inbox, execution; manager: all). saving queues a rebuild.</p>
<div id="tool-groups-section">
<p class="meta">loading…</p>
</div>
</section>
<!-- SCH3DUL3S: scheduled prompts. Creation + edit are folded
into the same table — empty bottom row is the create form
(fill cells, click ), inline-edit-row expands on the `✎`
toggle for existing schedules. Schedules list driven by
GET /api/schedules; POST /api/schedules to create, PATCH
/api/schedules/{id} to edit, POST /api/schedules/{id}/cancel
for per-target / whole-row cancel. Live updates via
`schedules_changed` SSE; tab activation re-fetches as a
safety net for disconnect windows.
See docs/web-ui.md::SCH3DUL3S tab. -->
<section class="tab-pane" id="tab-pane-schedules" data-tab-pane="schedules" hidden
role="tabpanel" aria-labelledby="tab-schedules">
<h2>◆ SCH3DUL3S ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">all schedules currently in the table. fill the bottom row to queue a new schedule (operator-direct, no approval gate; recurring when an interval is set). cancel a single target with the row button or the whole schedule with <code>✕ cancel all</code>.</p>
<div id="schedules-section">
<p class="meta">loading…</p>
</div>
<!-- QU3U3D R3M1ND3RS: self-scheduled agent reminders. Lives
on this tab so the operator has one place for everything
that fires at a future time — operator-set schedules
above, agent-self reminders here. Backed by GET
/api/reminders; live updates via `reminders_changed` SSE;
refreshReminders() called from refreshState + tab
activation as safety net for disconnect windows. -->
<h2>◆ QU3U3D R3M1ND3RS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">reminders agents have queued for themselves but not yet delivered. cancel to drop a stuck or unwanted entry.</p>
<div id="reminders-section">
<p class="meta">loading…</p>
</div>
</section>
<!-- ST4TS: hive-wide turn-stats aggregate. Plain tables/bars (the
dashboard bundle has no chart lib); data from GET
/api/stats-hive?window=, fetched on tab activation + on
window change. Per-agent trend charts stay on each agent's
own /stats page. -->
<section class="tab-pane" id="tab-pane-stats" data-tab-pane="stats" hidden
role="tabpanel" aria-labelledby="tab-stats">
<h2>◆ ST4TS ◆</h2>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p class="meta">hive-wide turn statistics, aggregated across every agent over the selected window. <strong>cost is a rough estimate</strong> from approximate per-model list prices — it drifts and is a ballpark, not a bill.</p>
<div class="hive-stats-windows" id="hive-stats-windows">
<button type="button" class="btn" data-w="1h">1h</button>
<button type="button" class="btn" data-w="4h">4h</button>
<button type="button" class="btn active" data-w="24h">24h</button>
<button type="button" class="btn" data-w="3d">3d</button>
<button type="button" class="btn" data-w="7d">7d</button>
<button type="button" class="btn" data-w="30d">30d</button>
</div>
<div class="hive-stats-chips" id="hive-stats-summary"></div>
<h3>◇ busiest agents</h3>
<div id="hive-stats-agents"><p class="meta">loading…</p></div>
<h3>◇ model mix (turns across the swarm)</h3>
<div id="hive-stats-models"></div>
<!-- "favorite tools": most-run bash commands across the swarm.
Header + list hidden until the hive-bash-mcp capture has
recorded data, so the section never shows an empty block. -->
<h3 id="hive-stats-bash-h" hidden>◇ favorite tools (bash commands across the swarm)</h3>
<div id="hive-stats-bash" hidden></div>
</section>
<!-- FL0W: lives on its own page now (`/flow.html`). The
message-flow + inbox + compose DOM only exists there — when
tabs.js boots on this page the corresponding renderers
no-op silently (each guard is `if (!el) return`). -->
<!-- S3TT1NGS: operator-local preferences (browser-notification
toggle) live on their own page now (`/settings.html`), reached
from the H0M3 hub. The notif buttons moved there verbatim;
`NOTIF.show()` on this page still fires (it reads the same
localStorage + browser permission the settings page sets). -->
</main>
<footer>
<pre class="banner banner-thin">░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░</pre>
<div class="divider">══════════════════════════════════════════════════════════════</div>
<p>▲△▲ <a href="https://forge.darkest.space/hyperhive/hyperhive">hyperhive</a> ▲△▲ hive-c0re on this host ▲△▲</p>
</footer>
<!-- Slide-in detail panel. Long content (clicked file previews,
approval diffs, journald logs, applied config) opens here
instead of expanding inline. Singleton — JS swaps the title +
body and toggles `.open`. Lives outside the tab panes so it
overlays any active tab. -->
<div id="side-panel" class="side-panel" aria-hidden="true">
<div class="side-panel-backdrop" id="side-panel-backdrop"></div>
<aside class="side-panel-drawer" role="dialog" aria-modal="true"
aria-labelledby="side-panel-title">
<header class="side-panel-head">
<span class="side-panel-title" id="side-panel-title"></span>
<button type="button" class="side-panel-close" id="side-panel-close"
title="close (esc)"></button>
</header>
<div class="side-panel-body" id="side-panel-body"></div>
</aside>
</div>
<!-- Selection action bar. Sticky-bottom strip that slides into
view when one or more agent cards is selected (click the icon
to toggle). See docs/web-ui.md::Selection bar for the bulk
action gating + clear semantics. -->
<div id="selection-bar" class="selection-bar" hidden role="toolbar"
aria-label="bulk agent actions">
<span class="selection-count" id="selection-count"></span>
<span class="selection-names" id="selection-names"></span>
<span class="selection-actions" id="selection-actions"></span>
<button type="button" class="btn selection-clear" id="selection-clear"
title="clear selection (esc)">✕ clear</button>
</div>
<!-- Single bundled entry — tabs.js is the dashboard tabs surface;
flow.html has its own flow.js bundle. esbuild folds
@hive/shared/terminal.js and the marked npm package into
tabs.js. -->
<script type="module" src="/static/tabs.js" defer></script>
</body>
</html>