remove the hive dashboard's stats page and the price table behind it
The ST4TS page (/stats.html), GET /api/stats-hive, hive-c0re's hive_stats rollup and the modelPrices option that only fed its cost estimate are gone. Each agent's own /stats page and the turn-stats database it reads are unchanged. Setting modelPrices now fails evaluation with a message saying so.
This commit is contained in:
parent
1d3ab2fae5
commit
5e9a22cb38
12 changed files with 112 additions and 1207 deletions
|
|
@ -1,6 +1,5 @@
|
|||
// esbuild build for @hive/dashboard: one JS+CSS bundle per page (the
|
||||
// dashboard at `/`, stats at its own
|
||||
// `.html`), each named after its own entry point below — see the
|
||||
// dashboard at `/`), 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.
|
||||
//
|
||||
|
|
@ -29,7 +28,7 @@ mkdirSync(staticDir(""), { recursive: true });
|
|||
// follow-up once asset sizes warrant it). esbuild writes each entry
|
||||
// to `static/<name>.js` based on the entryPoint basename.
|
||||
await build({
|
||||
entryPoints: [src("tabs.js"), src("stats.js")],
|
||||
entryPoints: [src("tabs.js")],
|
||||
outdir: staticDir(""),
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
|
|
@ -58,7 +57,7 @@ await build({
|
|||
|
||||
// Stream-worker entry. Lives in a separate bundle: SharedWorker
|
||||
// scripts run in a different global (`self` is the worker scope, no
|
||||
// `window`) so they can't be inlined into tabs.js / stats.js. Output is
|
||||
// `window`) so they can't be inlined into tabs.js. Output is
|
||||
// at `static/stream-worker.js`; common.js's `openStream` references
|
||||
// `/static/stream-worker.js` as the SharedWorker URL. `format: 'iife'`
|
||||
// matches the classic-script load (`new SharedWorker(url, name)` with
|
||||
|
|
@ -89,7 +88,6 @@ for (const entry of [
|
|||
"theme.css",
|
||||
"common.css",
|
||||
"dashboard.css",
|
||||
"stats.css",
|
||||
]) {
|
||||
await build({
|
||||
entryPoints: [src(entry)],
|
||||
|
|
@ -100,8 +98,6 @@ for (const entry of [
|
|||
});
|
||||
}
|
||||
|
||||
for (const html of ["index.html", "stats.html"]) {
|
||||
copyFileSync(src(html), dist(html));
|
||||
}
|
||||
copyFileSync(src("index.html"), dist("index.html"));
|
||||
|
||||
console.log("dashboard build ok →", dist(""));
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
@import "@hive/shared/base.css";
|
||||
@import "@hive/shared/terminal.css";
|
||||
@import "@hive/shared/tabs.css";
|
||||
@import "@hive/shared/chrome.css";
|
||||
/* Shared pill/chip/badge shape (the "extract pill component" issue,
|
||||
resolved as shared CSS rather than a JS component) — see pill.css
|
||||
itself for the classnames it covers. */
|
||||
|
|
@ -271,8 +270,7 @@ code {
|
|||
carries the horizontal gutter. Full-width chrome (the tab strip, the
|
||||
footer) then spans edge-to-edge for free,
|
||||
with no negative-margin breakout hacks. Only matches on pages that opt
|
||||
in by adding the wrapper (index.html); ST4TS
|
||||
is full-bleed with its own `.<page>-main` padding. */
|
||||
in by adding the wrapper (index.html). */
|
||||
.page-content {
|
||||
padding: 0 1.5em;
|
||||
}
|
||||
|
|
@ -285,31 +283,3 @@ code {
|
|||
top: 0;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* ─── stats table ─────────────────────────────────────────────────
|
||||
Shared right-aligned numeric table. Used by /stats.html (busiest
|
||||
agents) — moved here from dashboard.css when ST4TS became its own
|
||||
page. */
|
||||
.hive-stats-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.hive-stats-table th,
|
||||
.hive-stats-table td {
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 5px 8px;
|
||||
text-align: right;
|
||||
}
|
||||
.hive-stats-table th:first-child,
|
||||
.hive-stats-table td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
.hive-stats-table th {
|
||||
color: var(--muted);
|
||||
font-weight: normal;
|
||||
}
|
||||
.hive-stats-table td.num {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -600,7 +600,3 @@ footer .banner-thin {
|
|||
body.dashboard-shell.has-selection {
|
||||
padding-bottom: 4.5em;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
|
|
|
|||
|
|
@ -55,15 +55,10 @@
|
|||
|
||||
<!-- (Peer hives are no longer a tab — they render as a headline
|
||||
under 4G3NTS. See tab-pane-agents.) -->
|
||||
|
||||
<!-- ST4TS is a standalone page reached from the pages
|
||||
menu at the right of this strip, not tabs — the strip holds only
|
||||
in-page tabs. -->
|
||||
</nav>
|
||||
<!-- The pages menu and the profile menu, together at the right.
|
||||
Pages menu: the standalone pages (stats, API docs) and the swarm UI,
|
||||
filled by links-menu.js from /api/state; the pages link back to `/`,
|
||||
which is this dashboard. Profile menu: the signed-in user, filled by
|
||||
Pages menu: the API docs and the swarm UI, filled by links-menu.js
|
||||
from /api/state. Profile menu: the signed-in user, filled by
|
||||
user-menu.js. -->
|
||||
<div class="dashboard-chrome-actions">
|
||||
<div class="links-menu-slot" id="links-menu"></div>
|
||||
|
|
@ -148,11 +143,6 @@
|
|||
<p class="meta">loading…</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ST4TS lives on its own page (`/stats.html`).
|
||||
Its DOM only exists there — when tabs.js boots
|
||||
on this page the corresponding renderers no-op silently (each
|
||||
guard is `if (!el) return`). -->
|
||||
</main>
|
||||
</div>
|
||||
<!-- /.page-content -->
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Pages menu in the dashboard chrome: one icon button, beside the profile
|
||||
// avatar at the right, that opens a popover linking the standalone pages
|
||||
// (stats, API docs) and the swarm UI. It is `@hive/shared`'s `LinksMenu`, the
|
||||
// (API docs) and the swarm UI. It is `@hive/shared`'s `LinksMenu`, the
|
||||
// same component the swarm UI mounts for its service links, rendered with
|
||||
// Preact like the profile menu. The swarm entry only exists when `/api/state`
|
||||
// carries `swarm_public_url`, so a hive without a configured swarm domain
|
||||
|
|
@ -10,11 +10,10 @@ import { h, render } from "preact";
|
|||
import { LinksMenu } from "@hive/shared/links-menu.js";
|
||||
import { $ } from "./common.js";
|
||||
|
||||
/** Menu entries for a state snapshot. The two pages open in place; the swarm
|
||||
/** Menu entries for a state snapshot. The API docs open in place; the swarm
|
||||
* UI is another site, so it opens in a new tab. */
|
||||
export function menuLinks(state) {
|
||||
const links = [
|
||||
{ icon: "📊", label: "stats", url: "/stats.html", external: false },
|
||||
{ icon: "🧬", label: "api docs", url: "/api/docs/", external: false },
|
||||
];
|
||||
if (state && state.swarm_public_url) {
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
/* ─── /stats.html — hive-wide turn-stats rollup ────────────────────
|
||||
Extracted from the dashboard ST4TS tab. Minimal chrome:
|
||||
the back-link header +
|
||||
title (.page-header / .page-back / .page-title) live in
|
||||
@hive/shared/chrome.css.
|
||||
`.hive-stats-table` lives in common.css. Only the ST4TS-specific
|
||||
window selector, summary chips, and bars are page-specific. */
|
||||
|
||||
body.stats-shell {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stats-main {
|
||||
padding: 1.2em 1.5em 2em;
|
||||
}
|
||||
|
||||
/* Window selector — now lives in the page-header (same row as ← home /
|
||||
ST4TS title). Fills the remaining header space and right-aligns the
|
||||
buttons. The shared `.btn` pill look is kept; createTabStrip flags the
|
||||
active one with `.hive-tab--active`. */
|
||||
.hive-stats-windows {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
justify-content: flex-end;
|
||||
/* Smaller than the default .btn so the pills fit comfortably in the
|
||||
header without competing visually with the page title. */
|
||||
font-size: 0.82em;
|
||||
}
|
||||
/* Inactive buttons: slightly muted to distinguish from the active window,
|
||||
but readable even on low-contrast operator colour schemes. */
|
||||
.hive-stats-windows .btn {
|
||||
color: var(--subtext1, var(--muted));
|
||||
border-color: var(--border);
|
||||
font-size: 1em; /* relative to the .hive-stats-windows 0.82em */
|
||||
}
|
||||
.hive-stats-windows .btn.hive-tab--active {
|
||||
border-color: var(--amber);
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
.hive-stats-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.hive-stats-chip {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
padding: 6px 10px;
|
||||
background: var(--bg-elev);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
min-width: 7em;
|
||||
}
|
||||
.hive-stats-chip .k {
|
||||
font-size: 0.72rem;
|
||||
color: var(--muted);
|
||||
text-transform: lowercase;
|
||||
}
|
||||
.hive-stats-chip .v {
|
||||
font-size: 1.05rem;
|
||||
color: var(--fg);
|
||||
}
|
||||
.hive-stats-chip.est .v {
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
.hive-stats-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 12em 1fr 4em;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 3px 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.hive-stats-bar .track {
|
||||
background: var(--bg-elev);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
height: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hive-stats-bar .fill {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: var(--purple);
|
||||
}
|
||||
.hive-stats-bar .cnt {
|
||||
text-align: right;
|
||||
color: var(--muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>hyperhive // ST4TS</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/stats.css" />
|
||||
</head>
|
||||
<body class="stats-shell">
|
||||
<!-- Minimal chrome: back link + title —
|
||||
no full dashboard tabbar. Back link points to the
|
||||
dashboard (served at /). -->
|
||||
<header class="page-header">
|
||||
<a class="page-back" href="/">← dashboard</a>
|
||||
<span class="page-title">ST4TS</span>
|
||||
<!-- Window selector lives in the header so it behaves like a tab strip.
|
||||
Hash-routed via createTabStrip. -->
|
||||
<nav class="hive-stats-windows" id="hive-stats-windows" role="tablist">
|
||||
<button type="button" class="btn" data-tab="1h">1h</button>
|
||||
<button type="button" class="btn" data-tab="4h">4h</button>
|
||||
<button type="button" class="btn" data-tab="24h">24h</button>
|
||||
<button type="button" class="btn" data-tab="3d">3d</button>
|
||||
<button type="button" class="btn" data-tab="7d">7d</button>
|
||||
<button type="button" class="btn" data-tab="30d">30d</button>
|
||||
<button type="button" class="btn" data-tab="all">all</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="stats-main">
|
||||
<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-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-daemon 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>
|
||||
<!-- most-triggered skills across the swarm. Header + list hidden until
|
||||
at least one agent has actually invoked a skill. -->
|
||||
<h3 id="hive-stats-skills-h" hidden>
|
||||
◇ skill mix (invocations across the swarm)
|
||||
</h3>
|
||||
<div id="hive-stats-skills" hidden></div>
|
||||
</main>
|
||||
|
||||
<script type="module" src="/static/stats.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
// /stats.html — hive-wide turn-stats rollup.
|
||||
//
|
||||
// Extracted from the dashboard ST4TS tab. Pull-only (no SSE): fetched
|
||||
// from /api/stats-hive on load and on window change. Plain tables/bars —
|
||||
// this bundle has no chart lib; per-agent trend charts live on each
|
||||
// agent's own /stats page. The window selector is a hash-routed
|
||||
// createTabStrip (#1h / #24h / …), matching the per-agent stats page.
|
||||
import { $, initStickyTop } from "./common.js";
|
||||
import { createTabStrip } from "@hive/shared/tabs.js";
|
||||
|
||||
let hiveStatsWindow = "24h";
|
||||
|
||||
function hsFmtInt(n) {
|
||||
return Number.isFinite(n)
|
||||
? new Intl.NumberFormat().format(Math.round(n))
|
||||
: "0";
|
||||
}
|
||||
function hsFmtTokens(n) {
|
||||
if (!Number.isFinite(n)) return "0";
|
||||
if (n >= 1e9) return (n / 1e9).toFixed(2) + "B";
|
||||
if (n >= 1e6) return (n / 1e6).toFixed(2) + "M";
|
||||
if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
|
||||
return String(Math.round(n));
|
||||
}
|
||||
function hsFmtUsd(n) {
|
||||
if (!Number.isFinite(n)) return "$0";
|
||||
if (n >= 100) return "$" + n.toFixed(0);
|
||||
if (n >= 1) return "$" + n.toFixed(2);
|
||||
return "$" + n.toFixed(3);
|
||||
}
|
||||
function hsChip(parent, label, value, est) {
|
||||
const c = document.createElement("span");
|
||||
c.className = "hive-stats-chip" + (est ? " est" : "");
|
||||
const k = document.createElement("span");
|
||||
k.className = "k";
|
||||
k.textContent = label;
|
||||
const v = document.createElement("span");
|
||||
v.className = "v";
|
||||
v.textContent = value;
|
||||
c.append(k, v);
|
||||
parent.append(c);
|
||||
}
|
||||
function hsMeta(parent, text) {
|
||||
parent.replaceChildren();
|
||||
const p = document.createElement("p");
|
||||
p.className = "meta";
|
||||
p.textContent = text;
|
||||
parent.append(p);
|
||||
}
|
||||
// Shared bar-list renderer for any `KeyCount[]` mix (model/bash/skill).
|
||||
function renderKeyCountBars(container, mix) {
|
||||
container.replaceChildren();
|
||||
const max = mix[0].count || 1;
|
||||
for (const kc of mix) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "hive-stats-bar";
|
||||
const lbl = document.createElement("span");
|
||||
lbl.className = "lbl";
|
||||
lbl.textContent = kc.key;
|
||||
const track = document.createElement("span");
|
||||
track.className = "track";
|
||||
const fill = document.createElement("span");
|
||||
fill.className = "fill";
|
||||
fill.style.width = Math.max(2, Math.round((100 * kc.count) / max)) + "%";
|
||||
track.append(fill);
|
||||
const cnt = document.createElement("span");
|
||||
cnt.className = "cnt";
|
||||
cnt.textContent = hsFmtInt(kc.count);
|
||||
row.append(lbl, track, cnt);
|
||||
container.append(row);
|
||||
}
|
||||
}
|
||||
// A mix section that hides itself (+ its header) until the backing
|
||||
// capture has recorded data, so the section never shows an empty block
|
||||
// while data is pre-capture. Used by bash_mix and skill_mix.
|
||||
function renderOptionalMix(containerId, headerId, mix) {
|
||||
const el = $(containerId);
|
||||
const h = $(headerId);
|
||||
if (!el) return;
|
||||
if (!mix.length) {
|
||||
el.replaceChildren();
|
||||
el.hidden = true;
|
||||
if (h) h.hidden = true;
|
||||
return;
|
||||
}
|
||||
el.hidden = false;
|
||||
if (h) h.hidden = false;
|
||||
renderKeyCountBars(el, mix);
|
||||
}
|
||||
|
||||
function renderHiveStats(s) {
|
||||
const sum = $("hive-stats-summary");
|
||||
if (sum) {
|
||||
sum.replaceChildren();
|
||||
hsChip(sum, "window", s.window);
|
||||
hsChip(sum, "active agents", hsFmtInt(s.active_agents));
|
||||
hsChip(sum, "turns", hsFmtInt(s.total_turns));
|
||||
const totalTok =
|
||||
(s.total_input_tokens || 0) +
|
||||
(s.total_output_tokens || 0) +
|
||||
(s.total_cache_read_tokens || 0) +
|
||||
(s.total_cache_creation_tokens || 0);
|
||||
hsChip(sum, "tokens", hsFmtTokens(totalTok));
|
||||
hsChip(sum, "input", hsFmtTokens(s.total_input_tokens));
|
||||
hsChip(sum, "output", hsFmtTokens(s.total_output_tokens));
|
||||
hsChip(sum, "cache read", hsFmtTokens(s.total_cache_read_tokens));
|
||||
hsChip(sum, "est cost", hsFmtUsd(s.est_cost_usd), true);
|
||||
}
|
||||
|
||||
const at = $("hive-stats-agents");
|
||||
if (at) {
|
||||
const agents = s.agents || [];
|
||||
if (!agents.length) {
|
||||
hsMeta(at, "no turns in window");
|
||||
} else {
|
||||
at.replaceChildren();
|
||||
const table = document.createElement("table");
|
||||
table.className = "hive-stats-table";
|
||||
table.innerHTML =
|
||||
"<thead><tr><th>agent</th><th>turns</th><th>input</th>" +
|
||||
"<th>output</th><th>cache read</th><th>est cost</th></tr></thead>";
|
||||
const tb = document.createElement("tbody");
|
||||
for (const a of agents) {
|
||||
const tr = document.createElement("tr");
|
||||
const cells = [
|
||||
a.name,
|
||||
hsFmtInt(a.turns),
|
||||
hsFmtTokens(a.input_tokens),
|
||||
hsFmtTokens(a.output_tokens),
|
||||
hsFmtTokens(a.cache_read_tokens),
|
||||
hsFmtUsd(a.est_cost_usd),
|
||||
];
|
||||
cells.forEach((txt, i) => {
|
||||
const td = document.createElement("td");
|
||||
if (i > 0) td.className = "num";
|
||||
td.textContent = txt;
|
||||
tr.append(td);
|
||||
});
|
||||
tb.append(tr);
|
||||
}
|
||||
table.append(tb);
|
||||
at.append(table);
|
||||
}
|
||||
}
|
||||
|
||||
const mm = $("hive-stats-models");
|
||||
if (mm) {
|
||||
const mix = s.model_mix || [];
|
||||
if (!mix.length) hsMeta(mm, "no turns in window");
|
||||
else renderKeyCountBars(mm, mix);
|
||||
}
|
||||
|
||||
// "favorite tools": most-run bash commands across the swarm.
|
||||
renderOptionalMix("hive-stats-bash", "hive-stats-bash-h", s.bash_mix || []);
|
||||
// Most-triggered skills across the swarm.
|
||||
renderOptionalMix(
|
||||
"hive-stats-skills",
|
||||
"hive-stats-skills-h",
|
||||
s.skill_mix || [],
|
||||
);
|
||||
}
|
||||
|
||||
async function refreshHiveStats() {
|
||||
try {
|
||||
const resp = await fetch(
|
||||
"/api/stats-hive?window=" + encodeURIComponent(hiveStatsWindow),
|
||||
);
|
||||
if (!resp.ok) throw new Error("http " + resp.status);
|
||||
renderHiveStats(await resp.json());
|
||||
} catch (e) {
|
||||
const at = $("hive-stats-agents");
|
||||
if (at) hsMeta(at, "stats fetch failed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
initStickyTop();
|
||||
|
||||
// Window selector → hash-routed createTabStrip (#1h / #24h / …). No panes
|
||||
// here (the selector swaps data, not DOM); paneFor() returns null for each
|
||||
// tab and the strip's `if (pane)` guard handles it. The initial show()
|
||||
// fires onShow once → sets the window + does the first fetch, so no
|
||||
// separate refreshHiveStats() call is needed.
|
||||
createTabStrip(document.getElementById("hive-stats-windows"), {
|
||||
defaultId: hiveStatsWindow,
|
||||
onShow: (w) => {
|
||||
hiveStatsWindow = w;
|
||||
refreshHiveStats();
|
||||
},
|
||||
});
|
||||
|
|
@ -258,9 +258,8 @@ import { renderUserMenu } from "./user-menu.js";
|
|||
// ─── tab routing ───────────────────────────────────────────────────────
|
||||
// Hash-based: `#agents` / `#rebuild` / `#kept`
|
||||
// activate the matching pane on the dashboard. Empty
|
||||
// hash defaults to 4G3NTS. ST4TS is NOT a tab — it is a separate page
|
||||
// (`/stats.html`) reached from the pages menu (links-menu.js). Tab routing
|
||||
// only applies when the tab DOM is present.
|
||||
// hash defaults to 4G3NTS. Tab routing only applies when the tab DOM is
|
||||
// present.
|
||||
// The shared hash-routed tab strip (@hive/shared/tabs.js) owns the
|
||||
// tab/pane toggle + aria-selected + routing; `activateTab` runs the
|
||||
// per-tab side-effects via the strip's onShow. Constructed below (after
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
/* ─── page chrome: back-link header ────────────────────────────────
|
||||
The minimal sticky "frosted bar" header used by every standalone page
|
||||
reached from the dashboard's pages menu — /stats. A back
|
||||
link to the dashboard on the left, then a page title and, on /stats, a
|
||||
sub-tab nav. The operator dashboard uses its own richer
|
||||
sticky chrome (`.dashboard-chrome`); these classes cover the simple
|
||||
The minimal sticky "frosted bar" header used by every standalone page of
|
||||
an agent's own UI (/stats, /screen). A back link on the left, then a page
|
||||
title and, on /stats, a sub-tab nav. The operator dashboard uses its own
|
||||
richer sticky chrome (`.dashboard-chrome`); these classes cover the simple
|
||||
pages.
|
||||
|
||||
Shared across packages via @hive/shared so a new page only links the
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// What the links menu shows for one link, without a DOM: where it opens and
|
||||
// what it is called. Shared by the swarm UI (its `/api/links`) and the hive
|
||||
// dashboard (its stats and API docs pages, and the swarm UI).
|
||||
// dashboard (its API docs page and the swarm UI).
|
||||
|
||||
/** One entry of the links menu. */
|
||||
export interface MenuLink {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ test("a link the menu was handed opens in a new tab, without a referrer", () =>
|
|||
|
||||
test("a page of the same site opens in place", () => {
|
||||
assert.deepEqual(
|
||||
linkAttrs({ label: "stats", url: "/stats.html", external: false }),
|
||||
linkAttrs({ label: "api docs", url: "/api/docs/", external: false }),
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue