diff --git a/docs/trust-boundary/security.md b/docs/trust-boundary/security.md index 14d1bfae..3181a3fe 100644 --- a/docs/trust-boundary/security.md +++ b/docs/trust-boundary/security.md @@ -247,7 +247,7 @@ bind-mount compatibility with user namespace UID mapping first. `hive-c0re` runs as the unprivileged system user `hive-core` (`/var/lib/hyperhive` owned by `hive-core:hive-core`). It can't -directly invoke `nixos-container`, `journalctl -M`, or act on a system +directly invoke `nixos-container` or act on a system unit (`systemctl reload nginx`) — those require root. `hive-priv` fills this gap. @@ -275,7 +275,6 @@ known operations; there is no arbitrary command pass-through: | `CreateContainer` / `UpdateContainer` | `nixos-container create/update --flake ` | | `DestroyContainer` | `nixos-container destroy ` | | `ListContainers` | `nixos-container list` | -| `ReadContainerJournal` | `journalctl -M -n [filters...]` | | `ReloadGatewayNginx` | `systemctl reload/start/reset-failed nginx` (host unit; the unit name is hard-coded, not a parameter) | | `WriteNspawnFlags` | write `/etc/nixos-containers/.conf` (bind-mount list + network isolation vars) | | `EnsureAgentSocketDir` | `mkdirat` `/run/hive-agent/` `0751 root:root` under an `O_NOFOLLOW` parent fd; leaves an existing directory alone, refuses anything else | @@ -313,8 +312,8 @@ writes to only the paths `hive-priv` legitimately needs. ### Privilege boundary summary -| Component | Runs as | Privilege needed for | -| ---------------------------- | -------------- | ------------------------------------------------------------ | -| `hive-c0re` | `hive-core` | broker, HTTP dashboard | -| `hive-priv` | `root` | container lifecycle, journal reads, bind mounts, cred writes | -| `hive-ag3nt` (per-container) | per-agent user | turn execution, MCP serving | +| Component | Runs as | Privilege needed for | +| ---------------------------- | -------------- | --------------------------------------------- | +| `hive-c0re` | `hive-core` | broker, HTTP dashboard | +| `hive-priv` | `root` | container lifecycle, bind mounts, cred writes | +| `hive-ag3nt` (per-container) | per-agent user | turn execution, MCP serving | diff --git a/docs/web-ui/README.md b/docs/web-ui/README.md index 061308fa..a9ea42cd 100644 --- a/docs/web-ui/README.md +++ b/docs/web-ui/README.md @@ -28,8 +28,7 @@ of your time. It's a single page with exactly four tabs: 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), **Logs** (`/logs.html`, per-agent and host -journals), **Stats** (`/stats.html`, swarm-wide usage stats), +the whole swarm), **Stats** (`/stats.html`, swarm-wide usage stats), **Builds** (`/builds.html`, the rebuild queue and build history), and **Core** (`/core.html`, tombstones and container resource use). Your local browser preferences (notifications) live in the dashboard's Y3R C4LL @@ -72,9 +71,10 @@ merging a config change or bumping a flake input. takes effect until you hit **save all** at the bottom of the tab; a save queues a rebuild for whichever agents actually changed. -**Read an agent's logs.** The Logs page's AGENT tab pulls a live -journald view for any agent + service; the per-agent `⋮` menu's -**journal logs →** link jumps straight there, pre-filtered. +**Read an agent's logs.** Logs aren't in the hive dashboard. Every +hive ships its agents', daemons' and infra containers' journals to the +swarm's VictoriaLogs; read them in Grafana's **Explore** — see +[`swarm/services.md`](../swarm/services.md#logs-victorialogs). **Set up a schedule.** Scheduled prompts are swarm-level: set them with `swarmctl schedule` ([`swarm/schedules.md`](../swarm/schedules.md)). diff --git a/docs/web-ui/css-vars.md b/docs/web-ui/css-vars.md index 08c55847..4bd90e65 100644 --- a/docs/web-ui/css-vars.md +++ b/docs/web-ui/css-vars.md @@ -12,7 +12,7 @@ swap touches only the first: what the app references and it **never changes on a swap**. Both files live in `frontend/packages/shared/src/`. Per-page stylesheets -(`common.css`, `dashboard.css`, `flow.css`, `logs.css`, `agent.css`) +(`common.css`, `dashboard.css`, `flow.css`, `agent.css`) reference the **semantic** names and must **not** redeclare them or reach for the raw `--baseNN` slots directly. (`base.css` holds only the shared `body` typography — it references the palette but no longer defines it.) diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index 32381ec4..07f2e4f9 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -12,7 +12,7 @@ hive-c0re serves the dashboard at `/dashboard.html` (with the home page at `/`). It has a fixed chrome header at the top and a `
` that shows exactly one tab pane at a time. The URL hash (`#swarm`, `#call`, `#permissions`, `#schedules`) drives which pane is -active; hash changes don't reload the page. FL0W, L0GS, and the other +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. @@ -21,7 +21,7 @@ from the dashboard tab strip. at `/`. Every surface links back to the hub rather than to each other. - **Tab strip**: `◆ SW4RM ◆`, `◆ Y3R C4LL ◆`, `◆ P3RM1SS10NS ◆`, `◆ SCH3DUL3S ◆`. In-page tabs only — the SYST3M panels live on the - standalone **C0R3** page (`/core.html`), and FL0W / L0GS / ST4TS + 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 pills on SW4RM (container count), Y3R C4LL (unread @@ -29,7 +29,7 @@ from the dashboard tab strip. - **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, L0GS, + on **every** page (dashboard + every stand-alone page — FL0W, H0M3, C0R3, BU1LDS, ST4TS), injected at the top of `` by `renderServerWarnings` in `common.js`. Driven by `state.server_warnings` — a list of `{ kind, level, message }` — and @@ -53,9 +53,9 @@ from the dashboard tab strip. cold-load and updates it live as broker `sent` events arrive; it's preserved when `hive_name` / `swarm_name` later replace the raw title. -The FL0W and L0GS pages use a slim header (a `← home` back-link + the -page title) rather than the dashboard tab strip — they're standalone -surfaces, not tab panes. +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 @@ -110,7 +110,7 @@ instead — it's agent-terminal-only, unrelated to this section. Passive / rare-interaction state, reached from the **Core** tile on the H0M3 hub (served at -`/core.html`), with the same minimal chrome as `/logs.html`: a `← home` +`/core.html`), with the same minimal chrome as `/builds.html`: a `← home` back-link + a `createTabStrip` sub-tab nav (**K3PT ST4T3** default, then **C0NT41N3R L04D**). The page is its own esbuild bundle (`core.js`) that cold-loads `/api/state` and subscribes to `/api/dashboard/stream` for @@ -400,7 +400,7 @@ tab activation and on each dashboard state load. Hive-wide turn statistics, aggregated across every agent's `hyperhive-turn-stats.sqlite` for the selected window. Not a dashboard tab — a standalone page reached from the **Stats** tile on the H0M3 -hub, same minimal chrome as `/flow.html` / `/logs.html`. Distinct from +hub, same minimal chrome as `/flow.html`. Distinct from each agent's own `/stats` page (which carries the per-agent trend charts): ST4TS is the swarm-level rollup. @@ -488,7 +488,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, Logs, Builds, Stats, +responsive grid of link tiles — Dashboard, Flow, Builds, 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 @@ -496,45 +496,6 @@ from the shared theme (Catppuccin Mocha via `common.css` + `theme.css`). `home.js` fills the swarm/hive identity line at the top. All dashboard sub-pages include a `← Home` back-link for navigation. -## L0GS page (`/logs.html`) - -A dedicated log-viewer page (not a tab pane — a separate HTML page), -reachable from the Logs tile on the H0M3 hub. Minimal chrome: -a `← home` back link and a three-item sub-tab strip. Tab -routing is hash-based (`#agent`, `#infra`, `#system`); default is -`#agent`. (Build log history lives on the BU1LDS page — see above.) - -**AGENT sub-tab** — per-container journald viewer. Two selects: agent -name (populated from `GET /api/state`) and unit filter -(`hive-agent.service` / `hive-mcp-http.service` / -`hive-bash-daemon.service` / `hive-matrix-daemon.service` / -`(full machine journal)`). Fetches -`GET /api/journal/{name}?unit=&lines=500` on selection change -or `↻ refresh`. Output rendered as a `
` block. A `?agent=`
-and/or `?unit=` URL param pre-selects the agent + unit on page
-load — the per-agent `⋮` menu's **journal logs →** entry uses this to
-deep-link directly to a specific agent's journal. A "fetched N ago"
-chip appears after the `↻ refresh` button following each successful
-fetch and ticks every 30 s.
-
-**INFRA sub-tab** — journald viewer for the three hive infrastructure
-containers (`hive-forge`, `hive-gateway`, `hive-matrix`), a
-fixed client-side list (`INFRA_NAMES` in `logs.js` — no dashboard API
-exposes just the name list). No unit filter (infra containers don't run
-the per-agent hive daemons) — always the full machine journal (or, for
-the gateway, the host journal filtered to its own unit). Fetches
-`GET /api/journal/{name}?lines=500`, same "fetched N ago" ticker as
-AGENT. A `?agent=` deep-link routes here instead of AGENT when
-the name is one of the three infra containers.
-
-**SYSTEM sub-tab** — host-side service logs. Unit selector
-(`hive-c0re.service` / `hive-priv.service`). Fetches
-`GET /api/journal-host?unit=&lines=500` on activation
-and on `↻ refresh`. Rendered as a `
` block. A "fetched N ago"
-chip ticks every 30 s. Available to the operator unconditionally (not
-capability-gated — the endpoint lives on the hive-c0re dashboard,
-behind the gateway).
-
 ## Container row
 
 A full-height **square agent icon** (5em, capped) on the left. The
@@ -700,9 +661,6 @@ per-agent actions and navigation links. Contents:
   Identical to the bulk actions on the selection bar but operate
   on one agent without requiring a selection.
 - `↻ R3BU1LD` — always available; queues a rebuild for this agent.
-- `journal logs →` — opens `/logs.html#agent?agent=` so the
-  operator lands directly in the AGENT log tab pre-filtered to this
-  container, without having to pick an agent from the dropdown.
 - `DESTR0Y` / `PURG3` — destructive, each prompts for confirmation
   via the themed dialog (see **Themed dialogs** below).
 - `deployed: ↗` — present when the agent has a `deployed_sha`
@@ -884,8 +842,6 @@ below — some endpoints aren't in it yet.
   stdout+stderr as `text/plain`. Used by the `↓ raw` / `↓ download
   raw` links in BU1LDS. Works on both finished and in-progress
   build logs.
-- `GET /api/journal/{name}?unit=&lines=` — journalctl viewer for
-  a managed container; rendered in the side panel.
 - `GET /static/marked.js` serves the vendored `marked` bundle used
   for markdown previews.
 - `GET /api/state-file?path=` — bounded
diff --git a/docs/web-ui/shape.md b/docs/web-ui/shape.md
index 42cd6499..91c60d6e 100644
--- a/docs/web-ui/shape.md
+++ b/docs/web-ui/shape.md
@@ -19,7 +19,7 @@
   from `@hive/shared` (was `hive-fr0nt`); the dashboard ships one
   CSS bundle per page (`colors.css` + `theme.css` + `common.css`,
   loaded by every page, plus a page-specific bundle —
-  `dashboard.css` / `flow.css` / `logs.css` / `home.css` /
+  `dashboard.css` / `flow.css` / `home.css` /
   `stats.css` / `core.css` / `builds.css`);
   `common.css` inlines `@hive/shared`'s
   `base.css` + `terminal.css` + `tabs.css` + `chrome.css` +
@@ -247,7 +247,7 @@ values rather than per-row caches.
 tagged with `data-restore-key=""` survives the
 refresh. `snapshotOpenDetails()` walks managed sections before
 render, `restoreOpenDetails()` re-applies after. Long-content
-drill-ins (file previews, diffs, journald logs) now open in the
+drill-ins (file previews, diffs) now open in the
 **side panel** (see below) rather than expanding inline. The
 dashboard's own answered-questions history list was the only
 `
` consumer and is gone @@ -261,8 +261,8 @@ that needs it rather than ripped out along with their one caller. swipes in from the right — a singleton `#side-panel` with a titled header, a close button, and a scrollable body. Closes on the button, selecting the backdrop, or `Escape`. `Panel.open(title, -node)` swaps the body; the JS builders for file previews and -journald logs all render into it. **The +node)` swaps the body; the JS builder for file previews +renders into it. **The drawer width is drag-to-resize**: a thin 6px hit-strip on the left edge captures pointer events, resizes the drawer in real-time (pointer capture keeps dragging even if the cursor diff --git a/frontend/packages/dashboard/build.mjs b/frontend/packages/dashboard/build.mjs index eb71ce1f..44a84400 100644 --- a/frontend/packages/dashboard/build.mjs +++ b/frontend/packages/dashboard/build.mjs @@ -1,5 +1,5 @@ // esbuild build for @hive/dashboard: one JS+CSS bundle per page (H0M3 at -// `/`, dashboard/flow/logs/core/builds at their own +// `/`, dashboard/flow/core/builds 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. @@ -35,7 +35,6 @@ await build({ entryPoints: [ src("tabs.js"), src("flow.js"), - src("logs.js"), src("home.js"), src("stats.js"), src("core.js"), @@ -101,7 +100,6 @@ for (const entry of [ "common.css", "dashboard.css", "flow.css", - "logs.css", "home.css", "stats.css", "core.css", @@ -120,7 +118,6 @@ for (const html of [ "index.html", "dashboard.html", "flow.html", - "logs.html", "stats.html", "core.html", "builds.html", diff --git a/frontend/packages/dashboard/src/agent-menu/hive-agent-menu.js b/frontend/packages/dashboard/src/agent-menu/hive-agent-menu.js index 83991a73..afa4d261 100644 --- a/frontend/packages/dashboard/src/agent-menu/hive-agent-menu.js +++ b/frontend/packages/dashboard/src/agent-menu/hive-agent-menu.js @@ -140,24 +140,6 @@ class HiveAgentMenu extends HTMLElement { const menuSep = () => el("li", { class: "agent-menu-sep", role: "separator" }); - // Navigation link item (opens in same tab by default). - const menuLink = (label, href, title) => { - const li = el("li", { role: "presentation" }); - const a = el( - "a", - { - class: "agent-menu-item", - href, - role: "menuitem", - title: title || "", - }, - label, - ); - a.addEventListener("click", close); - li.append(a); - return li; - }; - // Show only actions that are applicable in the current state. if (c.running) { dropdown.append( @@ -209,15 +191,6 @@ class HiveAgentMenu extends HTMLElement { action: "/api/rebuild/", confirm: `rebuild ${c.name}? hot-reloads the container.`, }), - menuSep(), - // Deep-link to the AGENT log tab pre-filtered to this container. - // The ?agent= param is read by logs.js on load and pre-selects this - // agent's journal without extra clicks. - menuLink( - "journal logs →", - `/logs.html?agent=${encodeURIComponent(c.name)}#agent`, - `view ${c.name} journal logs`, - ), ); dropdown.append( diff --git a/frontend/packages/dashboard/src/builds.css b/frontend/packages/dashboard/src/builds.css index c0fe2c09..a17106ae 100644 --- a/frontend/packages/dashboard/src/builds.css +++ b/frontend/packages/dashboard/src/builds.css @@ -1,14 +1,11 @@ /* ─── /builds.html — BU1LDS page ──────────────────────────────────── The build lifecycle hub: rebuild queue + live log, meta inputs, and - build log history on one page. Carved out of /core.html (rebuild - queue + meta inputs) and /logs.html (BUILD tab). + build log history on one page. Imports system-sections.css for rebuild queue + meta inputs + live-log - component styles, and logs.css for the build-logs-* component styles. - Body-scoped `.logs-shell` rules in logs.css don't apply here - (`.builds-shell` body); only the build-logs-* component rules are used. */ + component styles; the build-logs-* rules for the BUILD L0GS list are + below. */ @import "./system-sections.css"; -@import "./logs.css"; @import "@hive/shared/jobq-graph.css"; body.builds-shell { @@ -27,3 +24,116 @@ body.builds-shell { .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); +} diff --git a/frontend/packages/dashboard/src/builds.html b/frontend/packages/dashboard/src/builds.html index 4fbf9281..90edfd96 100644 --- a/frontend/packages/dashboard/src/builds.html +++ b/frontend/packages/dashboard/src/builds.html @@ -12,9 +12,7 @@ @@ -335,8 +333,8 @@

- diff --git a/frontend/packages/dashboard/src/flow.css b/frontend/packages/dashboard/src/flow.css index 833c2421..e76d5094 100644 --- a/frontend/packages/dashboard/src/flow.css +++ b/frontend/packages/dashboard/src/flow.css @@ -92,7 +92,7 @@ body.flow-shell .flow-main-slim { /* Agent filter: a select in the FL0W header narrows the timeline to messages involving one agent. Non-matching rows get - `.flow-hidden`; the select reuses the journal-unit control look. */ + `.flow-hidden`. */ .flow-filter { font-family: inherit; font-size: 0.85em; diff --git a/frontend/packages/dashboard/src/index.html b/frontend/packages/dashboard/src/index.html index ef00a4a6..c35ca817 100644 --- a/frontend/packages/dashboard/src/index.html +++ b/frontend/packages/dashboard/src/index.html @@ -64,14 +64,6 @@ > - - - - Logs - - agent · infra · system logs - - diff --git a/frontend/packages/dashboard/src/logs.css b/frontend/packages/dashboard/src/logs.css deleted file mode 100644 index 1e8a991a..00000000 --- a/frontend/packages/dashboard/src/logs.css +++ /dev/null @@ -1,233 +0,0 @@ -/* ─── /logs.html — log viewer ────────────────────────────────────── - Same minimal-chrome pattern as /flow.html but with a sub-tab strip - instead of a full dashboard tabbar. */ - -body.logs-shell { - margin: 0; - padding: 0; - /* Full-viewport flex column so the log pane can scroll internally - instead of the whole page scrolling. The sticky-top wrapper and - page-header shrink to their natural height; .logs-main takes the - rest and each visible .logs-pane fills it. */ - height: 100dvh; - display: flex; - flex-direction: column; - overflow: hidden; -} - -/* Base tab styling now lives in @hive/shared/tabs.css (.hive-tab*). The - logs strip only adds a layout delta: fill the header row next to the - ← home back-link. `min-width: 0` lets this flex:1 nav shrink below its - intrinsic content width instead of being bumped onto its own row under - the back-link (the `.page-header` is explicitly nowrap; see chrome.css). */ -.logs-tabbar { - flex: 1; - min-width: 0; -} - -.logs-main { - padding: 1.2em 1.5em 0; - flex: 1 1 0; - min-height: 0; - display: flex; - flex-direction: column; -} - -.logs-pane[hidden] { - display: none; -} -.logs-pane { - flex: 1 1 0; - min-height: 0; - display: flex; - flex-direction: column; - padding-bottom: 1.2em; -} -.logs-toolbar { - display: flex; - gap: 0.6em; - align-items: center; - margin-bottom: 0.9em; - flex-wrap: wrap; -} - -/* ─── per-container journald + build-log viewers ────────────────── - Both open inside the logs page: a toolbar above a scrollable - output area. `.journal-*` classes power the agent and system log - tabs; `.build-logs-*` power the build tab. */ - -.journal-controls { - flex: 0 0 auto; - display: flex; - gap: 0.5em; - align-items: center; -} -.journal-unit { - font-family: inherit; - font-size: 0.9em; - background: var(--bg-elev); - color: var(--fg); - border: 1px solid var(--border); - padding: 0.2em 0.4em; -} -.logs-toolbar .journal-unit { - background: var(--border); - border-color: var(--purple-dim); - border-radius: 4px; - padding: 0.25em 0.5em; - font-size: 0.85em; -} -.journal-refresh { - font-size: 0.75em; - padding: 0.15em 0.5em; -} -/* "fetched N ago" chip next to the refresh button on AGENT + SYSTEM tabs. */ -.logs-fetch-ts { - font-size: 0.8em; - color: var(--muted); -} -.journal-output { - margin: 0; - background: var(--crust); - color: var(--fg); - border: 1px solid var(--purple-dim); - padding: 0.5em 0.7em; - flex: 1 1 0; - min-height: 0; - overflow: auto; - font-size: 0.85em; - line-height: 1.4; - white-space: pre; - word-break: normal; -} - -/* ─── build-log list ────────────────────────────────────────────── - Build tab: toolbar above a scrollable list of log header rows; - clicking a row expands full stdout+stderr inline. */ -.build-logs-panel { - display: flex; - flex-direction: column; - height: 100%; - gap: 0.4em; -} -.build-logs-toolbar { - flex: 0 0 auto; - display: flex; - gap: 0.5em; - align-items: center; -} -.build-logs-refresh { - font-size: 0.75em; - padding: 0.15em 0.5em; -} -.build-logs-list { - list-style: none; - padding: 0; - margin: 0; - overflow-y: auto; - flex: 1 1 0; - min-height: 0; -} -.build-logs-loading, -.build-logs-empty, -.build-logs-error { - color: var(--muted); - font-size: 0.85em; - padding: 0.3em 0; -} -.build-logs-error { - color: var(--red); -} -.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); -} diff --git a/frontend/packages/dashboard/src/logs.html b/frontend/packages/dashboard/src/logs.html deleted file mode 100644 index f8ad20ac..00000000 --- a/frontend/packages/dashboard/src/logs.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - hyperhive // LOGS - - - - - - - - - - -
- -
-
- - - - -
-
-select an agent above
-
- - -
-
- - - -
-
-select a container above
-
- - -
-
- - - -
-
loading…
-
-
- - - - diff --git a/frontend/packages/dashboard/src/logs.js b/frontend/packages/dashboard/src/logs.js deleted file mode 100644 index c7862280..00000000 --- a/frontend/packages/dashboard/src/logs.js +++ /dev/null @@ -1,284 +0,0 @@ -// /logs.html entry point: log viewer with three sub-tabs (AGENT, INFRA, -// SYSTEM). Build log history lives at /builds.html, not here. -// AGENT — per-container journald viewer, backed by GET /api/journal/{name} -// INFRA — infra-container journald viewer (hive-forge, hive-gateway, …), -// same API but full machine journal only (no unit filter) -// SYSTEM — host service logs, backed by GET /api/journal-host -// -// Tab routing via URL hash (#agent, #infra, #system). Default: #agent. -// -// URL params `?agent=name` and `?unit=svc` pre-select the agent + unit. -// When `?agent=` names an infra container the INFRA tab is activated instead. -// Last-fetched timestamp is shown next to the refresh button on AGENT, -// INFRA, and SYSTEM tabs so the operator knows how stale the output is. - -import { $, fmtAgeSecs, initServerWarnings } from "./common.js"; -import { el } from "@hive/shared/dom.js"; -import "@hive/shared/hive-tab-strip.js"; - -(() => { - initServerWarnings(); - - // ─── tab routing ────────────────────────────────────────────────────── - // The shared hash-routed tab strip (@hive/shared/tabs.js) handles the - // active toggle + aria-selected + pane visibility + lazy fetches via - // onShow. Constructed in the init block below (after the fetch fns + - // element refs it depends on exist). `logTabs.active()` replaces the - // old `activeTab()`. - let logTabs; - - // ─── helpers ────────────────────────────────────────────────────────── - - // Format a last-fetched timestamp: "fetched just now" / "fetched 3m ago". - function fmtFetchTs(fetchedAt) { - const ageSecs = Math.floor((Date.now() - fetchedAt) / 1000); - return ( - "fetched " + (ageSecs < 5 ? "just now" : fmtAgeSecs(ageSecs) + " ago") - ); - } - - // ─── AGENT tab ──────────────────────────────────────────────────────── - - const agentSelect = $("agent-select"); - const agentUnitSelect = $("agent-unit-select"); - const agentRefresh = $("agent-refresh"); - const agentOutput = $("agent-output"); - const agentFetchTs = $("agent-fetch-ts"); - - let agentFetching = false; - let agentLastFetch = 0; - async function fetchAgent() { - if (!agentSelect || !agentOutput) return; - const name = agentSelect.value; - if (!name) { - agentOutput.textContent = "select an agent above"; - return; - } - if (agentFetching) return; - agentFetching = true; - agentOutput.textContent = "fetching…"; - if (agentFetchTs) agentFetchTs.hidden = true; - const unit = agentUnitSelect ? agentUnitSelect.value : ""; - const params = new URLSearchParams({ lines: "500" }); - if (unit) params.set("unit", unit); - try { - const resp = await fetch( - "/api/journal/" + encodeURIComponent(name) + "?" + params, - ); - const text = await resp.text(); - agentOutput.textContent = resp.ok - ? text || "(empty)" - : "error " + resp.status + "\n" + text; - agentOutput.scrollTop = agentOutput.scrollHeight; - if (resp.ok) { - agentLastFetch = Date.now(); - if (agentFetchTs) { - agentFetchTs.textContent = fmtFetchTs(agentLastFetch); - agentFetchTs.hidden = false; - } - } - } catch (err) { - agentOutput.textContent = "fetch failed: " + err; - } finally { - agentFetching = false; - } - } - - if (agentSelect) agentSelect.addEventListener("change", fetchAgent); - if (agentUnitSelect) agentUnitSelect.addEventListener("change", fetchAgent); - if (agentRefresh) agentRefresh.addEventListener("click", fetchAgent); - - // ─── INFRA tab ──────────────────────────────────────────────────────── - // Infra containers (hive-forge, hive-gateway, hive-matrix) don't - // run the per-agent hive daemons, so the unit filter is inapplicable. We - // always fetch the full machine journal for them. - - const infraSelect = $("infra-select"); - const infraRefresh = $("infra-refresh"); - const infraOutput = $("infra-output"); - const infraFetchTs = $("infra-fetch-ts"); - - let infraFetching = false; - let infraLastFetch = 0; - async function fetchInfra() { - if (!infraSelect || !infraOutput) return; - const name = infraSelect.value; - if (!name) { - infraOutput.textContent = "select a container above"; - return; - } - if (infraFetching) return; - infraFetching = true; - infraOutput.textContent = "fetching…"; - if (infraFetchTs) infraFetchTs.hidden = true; - const params = new URLSearchParams({ lines: "500" }); - try { - const resp = await fetch( - "/api/journal/" + encodeURIComponent(name) + "?" + params, - ); - const text = await resp.text(); - infraOutput.textContent = resp.ok - ? text || "(empty)" - : "error " + resp.status + "\n" + text; - infraOutput.scrollTop = infraOutput.scrollHeight; - if (resp.ok) { - infraLastFetch = Date.now(); - if (infraFetchTs) { - infraFetchTs.textContent = fmtFetchTs(infraLastFetch); - infraFetchTs.hidden = false; - } - } - } catch (err) { - infraOutput.textContent = "fetch failed: " + err; - } finally { - infraFetching = false; - } - } - - if (infraSelect) infraSelect.addEventListener("change", fetchInfra); - if (infraRefresh) infraRefresh.addEventListener("click", fetchInfra); - - // Fixed allowlist — the three hive infra services never change at - // runtime, and there's no dashboard API exposing just the name list - // (the one that used to, `/api/state`'s `infra_containers` field, was - // start/stop-panel-only and is gone). Mirrors `hive_priv_sock::InfraContainer::ALL`. - const INFRA_NAMES = ["hive-forge", "hive-gateway", "hive-matrix"]; - - // ─── container list init ────────────────────────────────────────────── - // Fetch /api/state once to populate the AGENT selector (agents only); - // INFRA is a fixed list (see INFRA_NAMES). Also handles the ?agent= / - // ?unit= deep-link, routing to the INFRA tab when the named container - // is an infra container. - async function loadContainerLists() { - if (infraSelect) { - infraSelect.replaceChildren(); - infraSelect.append(el("option", { value: "" }, "— select container —")); - for (const name of INFRA_NAMES) { - infraSelect.append(el("option", { value: name }, name)); - } - } - try { - const resp = await fetch("/api/state"); - if (!resp.ok) return; - const state = await resp.json(); - - // Populate AGENT selector (agents only — no infra optgroup). - if (agentSelect) { - agentSelect.replaceChildren(); - agentSelect.append(el("option", { value: "" }, "— select agent —")); - for (const c of state.containers || []) { - agentSelect.append(el("option", { value: c.name }, c.name)); - } - } - - // Deep-link: honour ?agent= and ?unit= URL params. - const urlAgent = new URLSearchParams(location.search).get("agent"); - const urlUnit = new URLSearchParams(location.search).get("unit"); - if (urlAgent) { - if (INFRA_NAMES.includes(urlAgent)) { - // Route to INFRA tab. - logTabs.show("infra"); - if (infraSelect) { - infraSelect.value = urlAgent; - fetchInfra(); - } - } else if (agentSelect) { - // Route to AGENT tab. - const found = Array.from(agentSelect.options).some( - (o) => o.value === urlAgent, - ); - if (found) { - agentSelect.value = urlAgent; - if (urlUnit && agentUnitSelect) { - const unitFound = Array.from(agentUnitSelect.options).some( - (o) => o.value === urlUnit, - ); - if (unitFound) agentUnitSelect.value = urlUnit; - } - fetchAgent(); - } - } - } - } catch { - /**/ - } - } - - // ─── SYSTEM tab ─────────────────────────────────────────────────────── - - const systemUnitSelect = $("system-unit-select"); - const systemRefresh = $("system-refresh"); - const systemOutput = $("system-output"); - const systemFetchTs = $("system-fetch-ts"); - - let systemFetching = false; - let systemLastFetch = 0; - async function fetchSystem() { - if (!systemOutput) return; - if (systemFetching) return; - systemFetching = true; - systemOutput.textContent = "fetching…"; - if (systemFetchTs) systemFetchTs.hidden = true; - const unit = systemUnitSelect - ? systemUnitSelect.value - : "hive-c0re.service"; - const params = new URLSearchParams({ lines: "500" }); - if (unit) params.set("unit", unit); - try { - const resp = await fetch("/api/journal-host?" + params); - const text = await resp.text(); - systemOutput.textContent = resp.ok - ? text || "(empty)" - : "error " + resp.status + "\n" + text; - systemOutput.scrollTop = systemOutput.scrollHeight; - if (resp.ok) { - systemLastFetch = Date.now(); - if (systemFetchTs) { - systemFetchTs.textContent = fmtFetchTs(systemLastFetch); - systemFetchTs.hidden = false; - } - } - } catch (err) { - systemOutput.textContent = "fetch failed: " + err; - } finally { - systemFetching = false; - } - } - - if (systemUnitSelect) - systemUnitSelect.addEventListener("change", fetchSystem); - if (systemRefresh) systemRefresh.addEventListener("click", fetchSystem); - - // ─── init ───────────────────────────────────────────────────────────── - - // Wire the shared tab strip now that fetchSystem + the element refs it - // needs are defined. Its initial show() paints the active pane and, if - // the deep-linked tab is SYSTEM, kicks off the lazy fetch via onShow. - // Default: AGENT. - logTabs = document.getElementById("logs-tabbar").configure({ - tabs: [ - { id: "agent", label: "AGENT" }, - { id: "infra", label: "INFRA" }, - { id: "system", label: "SYSTEM" }, - ], - defaultId: "agent", - onShow: (id) => { - if (id === "system") fetchSystem(); - }, - }); - loadContainerLists(); - - // Tick the last-fetched timestamps every 30s so "fetched 1m ago" stays - // accurate without a manual refresh. - setInterval(() => { - if (agentLastFetch && agentFetchTs && !agentFetchTs.hidden) { - agentFetchTs.textContent = fmtFetchTs(agentLastFetch); - } - if (infraLastFetch && infraFetchTs && !infraFetchTs.hidden) { - infraFetchTs.textContent = fmtFetchTs(infraLastFetch); - } - if (systemLastFetch && systemFetchTs && !systemFetchTs.hidden) { - systemFetchTs.textContent = fmtFetchTs(systemLastFetch); - } - }, 30_000); -})(); diff --git a/frontend/packages/dashboard/src/stats.css b/frontend/packages/dashboard/src/stats.css index e7c9f665..51de64a8 100644 --- a/frontend/packages/dashboard/src/stats.css +++ b/frontend/packages/dashboard/src/stats.css @@ -1,6 +1,6 @@ /* ─── /stats.html — hive-wide turn-stats rollup ──────────────────── Extracted from the dashboard ST4TS tab. Same minimal- - chrome pattern as /flow.html and /logs.html: the back-link header + + chrome pattern as /flow.html: the back-link header + title (.page-header / .page-back / .page-title) live in @hive/shared/chrome.css. `.hive-stats-table` also lives in common.css — it's shared with the diff --git a/frontend/packages/dashboard/src/stats.html b/frontend/packages/dashboard/src/stats.html index 8e730ff3..7a062909 100644 --- a/frontend/packages/dashboard/src/stats.html +++ b/frontend/packages/dashboard/src/stats.html @@ -11,14 +11,14 @@ -