Without a viewport meta tag, some browsers (notably Firefox with Fingerprinting Protection active) internally read screen.availWidth / screen.availHeight to compute the default viewport size, producing the console warning 'Fingerprinting Protection is altering screen.availWidth and screen.availHeight'. Fix: - Add <meta name="viewport" content="width=device-width, initial-scale=1"> to all five pages that were missing it (dashboard index/flow/logs, agent index/stats). screen.html already had it. - Replace window.innerWidth with document.documentElement.clientWidth in the side-panel drag-resize code in common.js. clientWidth returns the actual CSS layout viewport width and is not rounded by Firefox's Fingerprinting Protection, making the drag calculation correct even with privacy.resistFingerprinting enabled.
329 lines
18 KiB
HTML
329 lines
18 KiB
HTML
<!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/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">
|
||
<!-- Hive identity line: swarm/hive label. Hidden until JS populates
|
||
it from /api/state (hive_name + swarm_name). Falls back to
|
||
hostname when only the domain is configured. -->
|
||
<p class="banner-thin" id="hive-identity" hidden></p>
|
||
<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>
|
||
|
||
<!-- P33RS: peer hive navigation links. Hidden until at least one
|
||
peer is configured in `services.hyperhive.peers`. -->
|
||
<a class="tab" id="tab-peers" href="#peers" role="tab"
|
||
aria-controls="tab-pane-peers"
|
||
data-tab="peers" hidden>
|
||
<span class="tab-label">◆ P33RS ◆</span>
|
||
<span class="tab-count" id="tab-count-peers" hidden></span>
|
||
</a>
|
||
|
||
<!-- M4TR1X: optional matrix web client (fluffychat-web by
|
||
default) mounted at /matrix/ by the gateway when
|
||
`hyperhive.matrix.gui.enable = true`. Same-origin
|
||
navigation (the static dist is its own SPA). Hidden in
|
||
JS when `state.matrix_gui_enabled === false` so operators
|
||
without matrix-gui on don't see a dead link. See
|
||
docs/web-ui.md::Tab strip for the gating model and
|
||
docs/gateway.md for the matrix vhost + .well-known
|
||
auto-discovery. -->
|
||
<a class="tab tab-link" id="tab-matrix" href="/matrix/" hidden
|
||
title="open the matrix chat client (fluffychat-web)">
|
||
<span class="tab-label">◆ M4TR1X ◆ →</span>
|
||
</a>
|
||
|
||
<!-- FL0W is its own page (`/flow.html`), not a tab. The link
|
||
lives in the tab strip so it reads as a peer surface;
|
||
clicking navigates rather than swapping panes in place.
|
||
Count pill mirrors the dashboard's operator-inbox length
|
||
and is hidden when zero. See docs/web-ui.md::FL0W page. -->
|
||
<a class="tab tab-link" id="tab-flow" href="/flow.html"
|
||
title="open the all-agents chat in a dedicated full-page terminal">
|
||
<span class="tab-label">◆ FL0W ◆ →</span>
|
||
<span class="tab-count" id="tab-count-flow" hidden></span>
|
||
</a>
|
||
|
||
<!-- L0GS and S3TT1NGS: default-overflow — always live in the ⋮
|
||
dropdown, never in the main strip. `data-overflow="default"`
|
||
marks them so JS keeps them out of the bar regardless of
|
||
available width. -->
|
||
<a class="tab tab-link" id="tab-logs" href="/logs.html"
|
||
data-overflow="default"
|
||
title="open the log viewer">
|
||
<span class="tab-label">◆ L0GS ◆ →</span>
|
||
</a>
|
||
<a class="tab" id="tab-settings" href="#settings" role="tab"
|
||
aria-controls="tab-pane-settings"
|
||
data-tab="settings"
|
||
data-overflow="default">
|
||
<span class="tab-label">◆ S3TT1NGS ◆</span>
|
||
</a>
|
||
|
||
<!-- Overflow ⋮ button + dropdown. JS populates the dropdown with
|
||
tab clones and keeps the button hidden when the dropdown would
|
||
be empty. Gets `.tabbar-overflow-active` when the active tab
|
||
is inside. -->
|
||
<div class="tabbar-overflow" id="tabbar-overflow">
|
||
<button type="button" class="tabbar-overflow-btn" id="tabbar-overflow-btn"
|
||
aria-haspopup="menu" aria-expanded="false"
|
||
title="more tabs" hidden>⋮</button>
|
||
<ul class="tabbar-overflow-dropdown" id="tabbar-overflow-dropdown"
|
||
role="menu" hidden></ul>
|
||
</div>
|
||
</nav>
|
||
</header>
|
||
|
||
<!-- Tab panes. Exactly one is `.tab-pane-active` at a time;
|
||
JS toggles based on the URL hash + `hashchange` events. -->
|
||
<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"
|
||
role="tabpanel" aria-labelledby="tab-swarm">
|
||
<div id="containers-section">
|
||
<p class="meta">loading…</p>
|
||
</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"
|
||
role="tabpanel" aria-labelledby="tab-call">
|
||
<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"
|
||
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>
|
||
|
||
</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"
|
||
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>
|
||
|
||
<!-- P33RS: peer hive link cards. Rendered from state.peer_hives;
|
||
hidden when the list is empty. -->
|
||
<section class="tab-pane" id="tab-pane-peers"
|
||
role="tabpanel" aria-labelledby="tab-peers">
|
||
<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 id="peers-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. No SchedulesChanged SSE
|
||
event yet, so the list re-fetches on tab activation + after
|
||
each submit / cancel. See docs/web-ui.md::SCH3DUL3S tab. -->
|
||
<section class="tab-pane" id="tab-pane-schedules"
|
||
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; refresh handled by refreshReminders()
|
||
(called from refreshState). -->
|
||
<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>
|
||
|
||
<!-- 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. Today this is just the
|
||
browser-notification toggle (per-browser localStorage state).
|
||
The same IDs that used to live in the tab strip migrated
|
||
here unchanged so `NOTIF.bind()` in common.js wires them
|
||
without any JS change. -->
|
||
<section class="tab-pane" id="tab-pane-settings"
|
||
role="tabpanel" aria-labelledby="tab-settings">
|
||
<h2>◆ S3TT1NGS ◆</h2>
|
||
<div class="divider">══════════════════════════════════════════════════════════════</div>
|
||
<p class="meta">operator-local preferences. these live in the browser's localStorage — they do not sync between devices and do not survive a profile wipe.</p>
|
||
|
||
<h3>◇ browser notifications</h3>
|
||
<p class="meta">desktop notifications for new approvals, new operator questions, and broker messages addressed to you. requires a secure context (https or localhost). mute silences the notifications without revoking the OS-level permission.</p>
|
||
<div id="notif-row" class="notif-row">
|
||
<button type="button" id="notif-enable" class="btn btn-notif" hidden>🔔 enable notifications</button>
|
||
<button type="button" id="notif-mute" class="btn btn-notif" hidden>🔕 mute</button>
|
||
<button type="button" id="notif-unmute" class="btn btn-notif" hidden>🔔 unmute</button>
|
||
<span id="notif-status" class="meta" hidden></span>
|
||
</div>
|
||
</section>
|
||
|
||
</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>
|