frontend: add <hive-tab-strip>, convert logs/credentials/core/builds tabbars
Every sub-page tabbar (logs.html, credentials.html, core.html,
builds.html) hand-wrote the same <nav class="hive-tabbar"><a
class="hive-tab">...</a></nav> boilerplate and then called
createTabStrip() on it after the fact. Add <hive-tab-strip>, a
markup-owning custom element (same reuse-boundary pattern as
<hive-menu>/<hive-side-panel>) that renders that markup from a
declarative tabs list, then wires the existing createTabStrip()
behaviour over what it just rendered — no behaviour duplication.
Convert all four sites to use it: each page's JS now calls
`.configure({ tabs, defaultId, onShow })` on the tabbar element instead
of `createTabStrip(el, opts)`, and configure() returns the identical
{ show, active } shape so nothing downstream changes. builds.js's
rebuild-queue count pill (builds-tab-count-rebuild) is expressed as a
tab's `badgeId` and renders nested in the same spot.
The dashboard's own tabbar and the two no-pane stats time-range
pickers are a different markup/behaviour shape and are intentionally
left alone.
This commit is contained in:
parent
44572d1e1a
commit
435ef193e1
10 changed files with 103 additions and 75 deletions
|
|
@ -16,25 +16,12 @@
|
||||||
meta inputs, and build log history on one page. Carved out of the
|
meta inputs, and build log history on one page. Carved out of the
|
||||||
old /core.html (rebuild queue + meta inputs) and /logs.html (BUILD
|
old /core.html (rebuild queue + meta inputs) and /logs.html (BUILD
|
||||||
tab) so the full build lifecycle is in one place. Same minimal
|
tab) so the full build lifecycle is in one place. Same minimal
|
||||||
chrome as /core.html — a `← home` back-link + a createTabStrip
|
chrome as /core.html — a `← home` back-link + a <hive-tab-strip>
|
||||||
sub-tab nav. Default sub-tab is the rebuild queue. -->
|
sub-tab nav. Default sub-tab is the rebuild queue. -->
|
||||||
<header class="page-header">
|
<header class="page-header">
|
||||||
<a class="page-back" href="/">← home</a>
|
<a class="page-back" href="/">← home</a>
|
||||||
<nav class="hive-tabbar builds-tabbar" id="builds-tabbar" role="tablist">
|
<hive-tab-strip class="hive-tabbar builds-tabbar" id="builds-tabbar" prefix="builds"
|
||||||
<a class="hive-tab" id="builds-tab-rebuild" href="#rebuild" role="tab"
|
role="tablist"></hive-tab-strip>
|
||||||
aria-controls="builds-pane-rebuild" data-tab="rebuild">
|
|
||||||
<span class="builds-tab-label">R3BU1LD QU3U3</span>
|
|
||||||
<span class="tab-count" id="builds-tab-count-rebuild" hidden></span>
|
|
||||||
</a>
|
|
||||||
<a class="hive-tab" id="builds-tab-meta" href="#meta" role="tab"
|
|
||||||
aria-controls="builds-pane-meta" data-tab="meta">
|
|
||||||
<span class="builds-tab-label">M3T4 1NPUTS</span>
|
|
||||||
</a>
|
|
||||||
<a class="hive-tab" id="builds-tab-buildlogs" href="#buildlogs" role="tab"
|
|
||||||
aria-controls="builds-pane-buildlogs" data-tab="buildlogs">
|
|
||||||
<span class="builds-tab-label">BUILD L0GS</span>
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="builds-main">
|
<main class="builds-main">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// BU1LDS page entry (/builds.html). The build lifecycle hub — rebuild
|
// BU1LDS page entry (/builds.html). The build lifecycle hub — rebuild
|
||||||
// queue, live build log, meta inputs, and build log history on one page.
|
// queue, live build log, meta inputs, and build log history on one page.
|
||||||
// Three sub-tabs via the shared createTabStrip (default = Rebuild Queue):
|
// Three sub-tabs via <hive-tab-strip> (default = Rebuild Queue):
|
||||||
// - R3BU1LD QU3U3: pending + running rebuilds + live build log inline
|
// - R3BU1LD QU3U3: pending + running rebuilds + live build log inline
|
||||||
// - M3T4 1NPUTS: nix flake input selector + meta-update trigger
|
// - M3T4 1NPUTS: nix flake input selector + meta-update trigger
|
||||||
// - BUILD L0GS: all-agents build log history (lazy-loaded on tab show)
|
// - BUILD L0GS: all-agents build log history (lazy-loaded on tab show)
|
||||||
|
|
@ -14,7 +14,7 @@ import { $, fmtAgeSecs, openStream, openBuildLogStream, initServerWarnings } fro
|
||||||
import { el } from '@hive/shared/dom.js';
|
import { el } from '@hive/shared/dom.js';
|
||||||
import { bindAsyncForms } from '@hive/shared/forms.js';
|
import { bindAsyncForms } from '@hive/shared/forms.js';
|
||||||
import { fmtAgo, fmtElapsed, fmtDuration, truncate } from './util.js';
|
import { fmtAgo, fmtElapsed, fmtDuration, truncate } from './util.js';
|
||||||
import { createTabStrip } from '@hive/shared/tabs.js';
|
import '@hive/shared/hive-tab-strip.js';
|
||||||
|
|
||||||
// ─── derived state ───────────────────────────────────────────────────────────
|
// ─── derived state ───────────────────────────────────────────────────────────
|
||||||
let metaInputsState = [];
|
let metaInputsState = [];
|
||||||
|
|
@ -777,7 +777,12 @@ async function init() {
|
||||||
initServerWarnings();
|
initServerWarnings();
|
||||||
bindAsyncForms(() => refreshState());
|
bindAsyncForms(() => refreshState());
|
||||||
|
|
||||||
buildTabs = createTabStrip(document.getElementById('builds-tabbar'), {
|
buildTabs = document.getElementById('builds-tabbar').configure({
|
||||||
|
tabs: [
|
||||||
|
{ id: 'rebuild', label: 'R3BU1LD QU3U3', badgeId: 'builds-tab-count-rebuild' },
|
||||||
|
{ id: 'meta', label: 'M3T4 1NPUTS' },
|
||||||
|
{ id: 'buildlogs', label: 'BUILD L0GS' },
|
||||||
|
],
|
||||||
defaultId: 'rebuild',
|
defaultId: 'rebuild',
|
||||||
onShow: (id) => {
|
onShow: (id) => {
|
||||||
if (id === 'buildlogs' && !buildLogsLoaded) {
|
if (id === 'buildlogs' && !buildLogsLoaded) {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
/core.html) carved out of the dashboard's old SYST3M tab so the
|
/core.html) carved out of the dashboard's old SYST3M tab so the
|
||||||
dashboard tab strip stays lean. Same minimal chrome as
|
dashboard tab strip stays lean. Same minimal chrome as
|
||||||
/logs.html — a `← home` back-link to the H0M3 hub + a
|
/logs.html — a `← home` back-link to the H0M3 hub + a
|
||||||
createTabStrip sub-tab nav. Three sub-tabs: kept state (tombstones),
|
<hive-tab-strip> sub-tab nav. Three sub-tabs: kept state (tombstones),
|
||||||
container load, and infra containers (start/stop/restart the four
|
container load, and infra containers (start/stop/restart the four
|
||||||
hive infra containers). Rebuild queue + meta inputs have moved to
|
hive infra containers). Rebuild queue + meta inputs have moved to
|
||||||
/builds.html (the build lifecycle hub). Default tab: K3PT ST4T3.
|
/builds.html (the build lifecycle hub). Default tab: K3PT ST4T3.
|
||||||
|
|
@ -24,20 +24,8 @@
|
||||||
infra-containers-section) match what core.js's renderers target. -->
|
infra-containers-section) match what core.js's renderers target. -->
|
||||||
<header class="page-header">
|
<header class="page-header">
|
||||||
<a class="page-back" href="/">← home</a>
|
<a class="page-back" href="/">← home</a>
|
||||||
<nav class="hive-tabbar core-tabbar" id="core-tabbar" role="tablist">
|
<hive-tab-strip class="hive-tabbar core-tabbar" id="core-tabbar" prefix="core"
|
||||||
<a class="hive-tab" id="core-tab-kept" href="#kept" role="tab"
|
role="tablist"></hive-tab-strip>
|
||||||
aria-controls="core-pane-kept" data-tab="kept">
|
|
||||||
<span class="core-tab-label">K3PT ST4T3</span>
|
|
||||||
</a>
|
|
||||||
<a class="hive-tab" id="core-tab-load" href="#load" role="tab"
|
|
||||||
aria-controls="core-pane-load" data-tab="load">
|
|
||||||
<span class="core-tab-label">C0NT41N3R L04D</span>
|
|
||||||
</a>
|
|
||||||
<a class="hive-tab" id="core-tab-infra" href="#infra" role="tab"
|
|
||||||
aria-controls="core-pane-infra" data-tab="infra">
|
|
||||||
<span class="core-tab-label">1NFR4</span>
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="core-main">
|
<main class="core-main">
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
// C0R3 page entry (/core.html). The host/coordinator surface carved out
|
// C0R3 page entry (/core.html). The host/coordinator surface carved out
|
||||||
// of the dashboard's old SYST3M tab into its own standalone page so the
|
// of the dashboard's old SYST3M tab into its own standalone page so the
|
||||||
// dashboard tab strip stays lean. Two sub-tabs via the shared
|
// dashboard tab strip stays lean. Two sub-tabs via <hive-tab-strip>
|
||||||
// createTabStrip (default = K3PT ST4T3): kept state (tombstones) and
|
// (default = K3PT ST4T3): kept state (tombstones) and container load.
|
||||||
// container load.
|
|
||||||
//
|
//
|
||||||
// Rebuild queue + meta inputs have moved to /builds.html (the build
|
// Rebuild queue + meta inputs have moved to /builds.html (the build
|
||||||
// lifecycle hub). This page is its own esbuild bundle with its own
|
// lifecycle hub). This page is its own esbuild bundle with its own
|
||||||
|
|
@ -13,7 +12,7 @@
|
||||||
import { $, form, openStream, initServerWarnings } from './common.js';
|
import { $, form, openStream, initServerWarnings } from './common.js';
|
||||||
import { el } from '@hive/shared/dom.js';
|
import { el } from '@hive/shared/dom.js';
|
||||||
import { asyncBtn, bindAsyncForms } from '@hive/shared/forms.js';
|
import { asyncBtn, bindAsyncForms } from '@hive/shared/forms.js';
|
||||||
import { createTabStrip } from '@hive/shared/tabs.js';
|
import '@hive/shared/hive-tab-strip.js';
|
||||||
|
|
||||||
// ─── derived state (own copies; this bundle has its own runtime) ──────────
|
// ─── derived state (own copies; this bundle has its own runtime) ──────────
|
||||||
let tombstonesState = [];
|
let tombstonesState = [];
|
||||||
|
|
@ -490,7 +489,12 @@ async function init() {
|
||||||
// Hash-routed sub-tab strip; default K3PT ST4T3. Container-load
|
// Hash-routed sub-tab strip; default K3PT ST4T3. Container-load
|
||||||
// polling runs only while the LOAD sub-tab is open (cpu is a short
|
// polling runs only while the LOAD sub-tab is open (cpu is a short
|
||||||
// two-sample read each refresh on the server).
|
// two-sample read each refresh on the server).
|
||||||
createTabStrip(document.getElementById('core-tabbar'), {
|
document.getElementById('core-tabbar').configure({
|
||||||
|
tabs: [
|
||||||
|
{ id: 'kept', label: 'K3PT ST4T3' },
|
||||||
|
{ id: 'load', label: 'C0NT41N3R L04D' },
|
||||||
|
{ id: 'infra', label: '1NFR4' },
|
||||||
|
],
|
||||||
defaultId: 'kept',
|
defaultId: 'kept',
|
||||||
onShow: (id) => {
|
onShow: (id) => {
|
||||||
if (id === 'load') startContainerLoadPolling();
|
if (id === 'load') startContainerLoadPolling();
|
||||||
|
|
|
||||||
|
|
@ -17,20 +17,8 @@
|
||||||
link points to the H0M3 hub (served at /). -->
|
link points to the H0M3 hub (served at /). -->
|
||||||
<header class="page-header">
|
<header class="page-header">
|
||||||
<a class="page-back" href="/">← home</a>
|
<a class="page-back" href="/">← home</a>
|
||||||
<nav class="hive-tabbar cred-tabbar" id="cred-tabbar" role="tablist">
|
<hive-tab-strip class="hive-tabbar cred-tabbar" id="cred-tabbar" prefix="cred"
|
||||||
<a class="hive-tab" id="cred-tab-matrix" href="#matrix" role="tab"
|
role="tablist"></hive-tab-strip>
|
||||||
aria-controls="cred-pane-matrix" data-tab="matrix">
|
|
||||||
<span class="cred-tab-label">MATRIX</span>
|
|
||||||
</a>
|
|
||||||
<a class="hive-tab" id="cred-tab-github" href="#github" role="tab"
|
|
||||||
aria-controls="cred-pane-github" data-tab="github">
|
|
||||||
<span class="cred-tab-label">GITHUB</span>
|
|
||||||
</a>
|
|
||||||
<a class="hive-tab" id="cred-tab-forges" href="#forges" role="tab"
|
|
||||||
aria-controls="cred-pane-forges" data-tab="forges">
|
|
||||||
<span class="cred-tab-label">FORGES</span>
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="cred-main">
|
<main class="cred-main">
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
import { $, esc, fmtAgeSecs, renderServerWarnings } from './common.js';
|
import { $, esc, fmtAgeSecs, renderServerWarnings } from './common.js';
|
||||||
import { el } from '@hive/shared/dom.js';
|
import { el } from '@hive/shared/dom.js';
|
||||||
import { createTabStrip } from '@hive/shared/tabs.js';
|
import '@hive/shared/hive-tab-strip.js';
|
||||||
import { themedConfirm, themedToast } from '@hive/shared/modal.js';
|
import { themedConfirm, themedToast } from '@hive/shared/modal.js';
|
||||||
|
|
||||||
let agents = [];
|
let agents = [];
|
||||||
|
|
@ -507,7 +507,12 @@ async function init() {
|
||||||
$('gh-form').addEventListener('submit', submitGithub);
|
$('gh-form').addEventListener('submit', submitGithub);
|
||||||
$('ef-form').addEventListener('submit', submitForgeAccount);
|
$('ef-form').addEventListener('submit', submitForgeAccount);
|
||||||
|
|
||||||
createTabStrip(document.getElementById('cred-tabbar'), {
|
document.getElementById('cred-tabbar').configure({
|
||||||
|
tabs: [
|
||||||
|
{ id: 'matrix', label: 'MATRIX' },
|
||||||
|
{ id: 'github', label: 'GITHUB' },
|
||||||
|
{ id: 'forges', label: 'FORGES' },
|
||||||
|
],
|
||||||
defaultId: 'matrix',
|
defaultId: 'matrix',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,24 +19,8 @@
|
||||||
moved to /builds.html (the build lifecycle hub). -->
|
moved to /builds.html (the build lifecycle hub). -->
|
||||||
<header class="page-header">
|
<header class="page-header">
|
||||||
<a class="page-back" href="/">← home</a>
|
<a class="page-back" href="/">← home</a>
|
||||||
<nav class="hive-tabbar logs-tabbar" id="logs-tabbar" role="tablist">
|
<hive-tab-strip class="hive-tabbar logs-tabbar" id="logs-tabbar" prefix="logs"
|
||||||
<a class="hive-tab" id="logs-tab-agent" href="#agent" role="tab"
|
role="tablist"></hive-tab-strip>
|
||||||
aria-controls="logs-pane-agent" data-tab="agent">
|
|
||||||
<span class="logs-tab-label">AGENT</span>
|
|
||||||
</a>
|
|
||||||
<a class="hive-tab" id="logs-tab-infra" href="#infra" role="tab"
|
|
||||||
aria-controls="logs-pane-infra" data-tab="infra">
|
|
||||||
<span class="logs-tab-label">INFRA</span>
|
|
||||||
</a>
|
|
||||||
<a class="hive-tab" id="logs-tab-system" href="#system" role="tab"
|
|
||||||
aria-controls="logs-pane-system" data-tab="system">
|
|
||||||
<span class="logs-tab-label">SYSTEM</span>
|
|
||||||
</a>
|
|
||||||
<a class="hive-tab" id="logs-tab-audit" href="#audit" role="tab"
|
|
||||||
aria-controls="logs-pane-audit" data-tab="audit">
|
|
||||||
<span class="logs-tab-label">AUDIT</span>
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="logs-main">
|
<main class="logs-main">
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import {
|
||||||
} from './common.js';
|
} from './common.js';
|
||||||
import { el } from '@hive/shared/dom.js';
|
import { el } from '@hive/shared/dom.js';
|
||||||
import { epochSec } from './util.js';
|
import { epochSec } from './util.js';
|
||||||
import { createTabStrip } from '@hive/shared/tabs.js';
|
import '@hive/shared/hive-tab-strip.js';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
initServerWarnings();
|
initServerWarnings();
|
||||||
|
|
@ -368,7 +368,13 @@ import { createTabStrip } from '@hive/shared/tabs.js';
|
||||||
// needs are defined. Its initial show() paints the active pane and, if
|
// needs are defined. Its initial show() paints the active pane and, if
|
||||||
// the deep-linked tab is SYSTEM/AUDIT, kicks off the lazy fetch via onShow.
|
// the deep-linked tab is SYSTEM/AUDIT, kicks off the lazy fetch via onShow.
|
||||||
// Default: AGENT (build logs moved to /builds.html).
|
// Default: AGENT (build logs moved to /builds.html).
|
||||||
logTabs = createTabStrip(document.getElementById('logs-tabbar'), {
|
logTabs = document.getElementById('logs-tabbar').configure({
|
||||||
|
tabs: [
|
||||||
|
{ id: 'agent', label: 'AGENT' },
|
||||||
|
{ id: 'infra', label: 'INFRA' },
|
||||||
|
{ id: 'system', label: 'SYSTEM' },
|
||||||
|
{ id: 'audit', label: 'AUDIT' },
|
||||||
|
],
|
||||||
defaultId: 'agent',
|
defaultId: 'agent',
|
||||||
onShow: (id) => {
|
onShow: (id) => {
|
||||||
if (id === 'system') fetchSystem();
|
if (id === 'system') fetchSystem();
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"./terminal.js": "./src/terminal/terminal.js",
|
"./terminal.js": "./src/terminal/terminal.js",
|
||||||
"./tabs.js": "./src/tabs/tabs.js",
|
"./tabs.js": "./src/tabs/tabs.js",
|
||||||
"./tabs.css": "./src/tabs/tabs.css",
|
"./tabs.css": "./src/tabs/tabs.css",
|
||||||
|
"./hive-tab-strip.js": "./src/tabs/hive-tab-strip.js",
|
||||||
"./colors.css": "./src/colors.css",
|
"./colors.css": "./src/colors.css",
|
||||||
"./theme.css": "./src/theme.css",
|
"./theme.css": "./src/theme.css",
|
||||||
"./base.css": "./src/base.css",
|
"./base.css": "./src/base.css",
|
||||||
|
|
|
||||||
60
frontend/packages/shared/src/tabs/hive-tab-strip.js
Normal file
60
frontend/packages/shared/src/tabs/hive-tab-strip.js
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
// hive-tab-strip.js — <hive-tab-strip>, the markup-owning tab-strip custom
|
||||||
|
// element behind the logs/credentials/core/builds sub-page tabbars. Owns
|
||||||
|
// rendering the <a class="hive-tab"> markup from a declarative `tabs` list
|
||||||
|
// instead of every page hand-writing the same <nav><a>...</a></nav>
|
||||||
|
// boilerplate, then wires the existing createTabStrip() behaviour
|
||||||
|
// (@hive/shared/tabs.js) over what it just rendered — no behaviour
|
||||||
|
// duplication, this is markup only.
|
||||||
|
//
|
||||||
|
// Light-DOM (no shadow root): the existing .hive-tabbar/.hive-tab/
|
||||||
|
// .tab-count CSS (tabs.css + dashboard.css) already targets plain classes
|
||||||
|
// on the tabbar/tab elements directly, and tab *panes* stay page-owned
|
||||||
|
// content this element never touches — no slotting/scoping need the way
|
||||||
|
// <hive-menu>/<hive-side-panel> have.
|
||||||
|
//
|
||||||
|
// Host keeps its own id/class/role (same as the <nav> it replaces, so
|
||||||
|
// existing CSS selectors + #id lookups keep matching); pass a `prefix`
|
||||||
|
// attribute (e.g. `prefix="logs"`). `configure({ tabs, defaultId, onShow })`
|
||||||
|
// then renders one <a> per tab —
|
||||||
|
// <a class="hive-tab" id="${prefix}-tab-${id}" href="#${id}" role="tab"
|
||||||
|
// aria-controls="${prefix}-pane-${id}" data-tab="${id}">${label}</a>
|
||||||
|
// — same id/aria-controls scheme the hand-written markup used, so each
|
||||||
|
// page's panes (aria-labelledby="${prefix}-tab-${id}") don't need to
|
||||||
|
// change. A `badgeId` tab also gets a nested `<span class="tab-count"
|
||||||
|
// id="${badgeId}" hidden>` (matches builds.html's rebuild-queue count
|
||||||
|
// pill), then wires up createTabStrip() and returns exactly what it
|
||||||
|
// returns ({ show, active }) — every existing `.active()`/`.show(id)`
|
||||||
|
// call site keeps working unchanged. Call configure() exactly once per
|
||||||
|
// element (throws if called twice), mirroring the old call sites.
|
||||||
|
|
||||||
|
import { el } from '../dom.js';
|
||||||
|
import { createTabStrip } from './tabs.js';
|
||||||
|
|
||||||
|
class HiveTabStrip extends HTMLElement {
|
||||||
|
configure({ tabs, defaultId, onShow }) {
|
||||||
|
if (this._api) {
|
||||||
|
throw new Error('hive-tab-strip: configure() called twice on the same element');
|
||||||
|
}
|
||||||
|
const prefix = this.getAttribute('prefix');
|
||||||
|
if (!prefix) {
|
||||||
|
throw new Error('hive-tab-strip: missing required `prefix` attribute');
|
||||||
|
}
|
||||||
|
for (const tab of tabs) {
|
||||||
|
const a = el('a', {
|
||||||
|
class: 'hive-tab',
|
||||||
|
id: `${prefix}-tab-${tab.id}`,
|
||||||
|
href: `#${tab.id}`,
|
||||||
|
role: 'tab',
|
||||||
|
'aria-controls': `${prefix}-pane-${tab.id}`,
|
||||||
|
'data-tab': tab.id,
|
||||||
|
}, tab.label);
|
||||||
|
if (tab.badgeId) {
|
||||||
|
a.append(el('span', { class: 'tab-count', id: tab.badgeId, hidden: '' }));
|
||||||
|
}
|
||||||
|
this.append(a);
|
||||||
|
}
|
||||||
|
this._api = createTabStrip(this, { defaultId, onShow });
|
||||||
|
return this._api;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('hive-tab-strip', HiveTabStrip);
|
||||||
Loading…
Reference in a new issue