dashboard: replace <hive-jobq-graph> with a shared Preact component
Ports the shadow-DOM <hive-jobq-graph> custom element
(frontend/packages/shared/src/jobq-graph/) to a Preact component
(JobqGraph.js) shared by the dashboard and swarm-ui, per hyperhive#3310.
- JobqGraph.js: written with plain h() calls (no JSX) so the same file
compiles unmodified under both the dashboard's text-loader CSS config
and swarm-ui's JSX config. Exports `JobqGraph` for JSX use and
`mountJobqGraph(container, props)` for the dashboard's non-JSX
imperative mount, returning a `{refresh(), update()}` handle matching
the old custom element's public surface. Same rendering contract as
before: indented state tree, payload.label verbatim, payload.data as
a generic key/value list, "waits on: <label>" text for Node-kind deps,
per-state filter checkboxes, optional cancel button.
- jobq-graph.css: light-DOM adaptation of the old shadow-scoped
stylesheet (:host -> .jg-root, otherwise unchanged).
- dashboard/src/builds.js: local mountJobqGraph() renamed to
mountRebuildQueue() to avoid colliding with the newly-imported shared
mountJobqGraph; cancel handling is now a plain onCancel callback
instead of a DOM CustomEvent listener (no shadow boundary to cross
anymore).
- dashboard + shared package.json: added preact as a dependency (matches
swarm-ui's existing pin, 10.29.8) - the dashboard was a vanilla-JS MPA
with no Preact/JSX pipeline before this.
- Removed the old hive-jobq-graph.js/.css entirely (confirmed via grep
it had exactly one consumer, dashboard/src/builds.js, so this is a
clean swap, not parallel maintenance of two implementations).
- Updated stale doc-comment references to the old element name in
builds.html, tabs.js, swarm.js, docs/web-ui/dashboard.md, and
hive-c0re/src/job_queue/mod.rs.
Verified: npm run build (whole frontend workspace) and npm run
typecheck (swarm-ui) both clean; cargo build/clippy/test -p hive-c0re
all clean (331 tests, 0 failures); headless-chromium screenshot of
/builds.html against a mock GET /api/jobq/graph payload confirms full
visual/behavioral parity with the old custom element (tree, filter
checkboxes, cancel buttons, error text, waits-on line, data list, live
build log panel).
This covers the dashboard-replacement half of hyperhive#3310 only. The
swarm-ui half (rendering the CreateAgent DAG on the agent-creation page)
is downstream of hyperhive#3306/#3124 landing - no swarm-ui page exists
yet to mount it in.
This commit is contained in:
parent
bad5285f2e
commit
37161cd136
13 changed files with 344 additions and 336 deletions
|
|
@ -9,6 +9,7 @@
|
|||
(`.builds-shell` body); only the build-logs-* component rules are used. */
|
||||
@import "./system-sections.css";
|
||||
@import "./logs.css";
|
||||
@import "@hive/shared/jobq-graph.css";
|
||||
|
||||
body.builds-shell {
|
||||
margin: 0;
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@
|
|||
<main class="builds-main">
|
||||
|
||||
<!-- R3BU1LD QU3U3: pending + running rebuilds, meta-updates, and
|
||||
first-spawns. Rendered from GET /api/jobq/graph by
|
||||
<hive-jobq-graph>; `rebuild_queue_changed` over
|
||||
/api/dashboard/stream is the refresh trigger and carries no
|
||||
payload of its own. Default tab. -->
|
||||
first-spawns. Rendered from GET /api/jobq/graph by the
|
||||
`JobqGraph` Preact component (@hive/shared/jobq-graph.js);
|
||||
`rebuild_queue_changed` over /api/dashboard/stream is the
|
||||
refresh trigger and carries no payload of its own. Default tab. -->
|
||||
<section class="builds-pane" id="builds-pane-rebuild" data-tab-pane="rebuild"
|
||||
role="tabpanel" aria-labelledby="builds-tab-rebuild">
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -16,18 +16,17 @@ import { bindAsyncForms } from '@hive/shared/forms.js';
|
|||
import { themedConfirm } from '@hive/shared/modal.js';
|
||||
import { fmtAgo, fmtDuration, truncate } from './util.js';
|
||||
import '@hive/shared/hive-tab-strip.js';
|
||||
import '@hive/shared/jobq-graph.js';
|
||||
import { mountJobqGraph } from '@hive/shared/jobq-graph.js';
|
||||
|
||||
// ─── derived state ───────────────────────────────────────────────────────────
|
||||
let metaInputsState = [];
|
||||
let metaUpdateRunning = false;
|
||||
// Kept in sync from <hive-jobq-graph>'s `hive-jobq-graph-update` event
|
||||
// (see mountJobqGraph below) — the component owns fetching GET
|
||||
// /api/jobq/graph and renders the tree itself; this flat array exists only
|
||||
// for the two things it doesn't render: the count-pill and the live-log
|
||||
// panel.
|
||||
// Kept in sync from JobqGraph's `onUpdate` callback (see mountRebuildQueue
|
||||
// below) — the component owns fetching GET /api/jobq/graph and renders
|
||||
// the tree itself; this flat array exists only for the two things it
|
||||
// doesn't render: the count-pill and the live-log panel.
|
||||
let jobqNodes = [];
|
||||
let jobqGraphEl = null;
|
||||
let jobqGraphHandle = null;
|
||||
|
||||
function syncFromSnapshot(s) {
|
||||
metaInputsState = (s.meta_inputs || []).slice();
|
||||
|
|
@ -128,48 +127,50 @@ function renderMetaInputs(s) {
|
|||
}
|
||||
|
||||
// ─── rebuild queue ────────────────────────────────────────────────────────────
|
||||
// R3BU1LD QU3U3 is <hive-jobq-graph> directly (mara: "replace the build
|
||||
// queue tab with this component") — no hand-rolled tree/roll-up rendering
|
||||
// here anymore. The component owns fetching GET /api/jobq/graph and its
|
||||
// own refresh(); this page just listens for its `hive-jobq-graph-update`
|
||||
// event to keep `jobqNodes` (the flat array) in sync for the two things
|
||||
// the generic view doesn't render itself: the count-pill and the live-log
|
||||
// panel below.
|
||||
// R3BU1LD QU3U3 is `JobqGraph` (Preact, mounted imperatively — see
|
||||
// mountJobqGraph's own doc comment in @hive/shared/jobq-graph.js for why
|
||||
// this file has no JSX pipeline) directly — no hand-rolled tree/roll-up
|
||||
// rendering here anymore. The component owns fetching GET /api/jobq/graph
|
||||
// and its own refetch (`.refresh()` on the mount handle); this page just
|
||||
// reads its `onUpdate` callback to keep `jobqNodes` (the flat array) in
|
||||
// sync for the two things the generic view doesn't render itself: the
|
||||
// count-pill and the live-log panel below.
|
||||
//
|
||||
// Cancel is the one action this page *does* wire up: the
|
||||
// `cancellable` attribute turns on the component's own per-node cancel
|
||||
// button, which dispatches `hive-jobq-graph-cancel` rather than posting
|
||||
// anything — the endpoint (`/api/rebuild-queue/{id}/cancel`) is this
|
||||
// page's domain concept, not the generic component's.
|
||||
function mountJobqGraph() {
|
||||
// Cancel is the one action this page *does* wire up: the `cancellable`
|
||||
// prop turns on the component's own per-node cancel button, which calls
|
||||
// `onCancel(id)` rather than posting anything — the endpoint
|
||||
// (`/api/rebuild-queue/{id}/cancel`) is this page's domain concept, not
|
||||
// the generic component's.
|
||||
function mountRebuildQueue() {
|
||||
const root = $('rebuild-queue-section');
|
||||
if (!root) return;
|
||||
root.replaceChildren();
|
||||
jobqGraphEl = el('hive-jobq-graph', { endpoint: '/api/jobq/graph', cancellable: '' });
|
||||
jobqGraphEl.addEventListener('hive-jobq-graph-update', (e) => {
|
||||
jobqNodes = e.detail.nodes || [];
|
||||
renderRebuildLiveLog();
|
||||
updateRebuildCount();
|
||||
jobqGraphHandle = mountJobqGraph(root, {
|
||||
endpoint: '/api/jobq/graph',
|
||||
cancellable: true,
|
||||
onUpdate: (nodes) => {
|
||||
jobqNodes = nodes || [];
|
||||
renderRebuildLiveLog();
|
||||
updateRebuildCount();
|
||||
},
|
||||
onCancel: async (id) => {
|
||||
const node = jobqNodes.find((n) => n.id === id);
|
||||
const label = node ? node.payload.label : 'node ' + id;
|
||||
if (!(await themedConfirm({
|
||||
message: `cancel ${label}? a group root cancels the whole subtree; a mid-tree node cancels just that branch.`,
|
||||
danger: true, confirmLabel: '✕ cancel',
|
||||
}))) return;
|
||||
try {
|
||||
const r = await fetch('/api/rebuild-queue/' + id + '/cancel', { method: 'POST' });
|
||||
if (!r.ok) throw new Error('http ' + r.status);
|
||||
// No manual refresh: cancel flips node state, which fires
|
||||
// rebuild_queue_changed over SSE — the existing handler below
|
||||
// already calls jobqGraphHandle.refresh() on that tick.
|
||||
} catch (err) {
|
||||
console.error('cancel failed', err);
|
||||
}
|
||||
},
|
||||
});
|
||||
jobqGraphEl.addEventListener('hive-jobq-graph-cancel', async (e) => {
|
||||
const { id } = e.detail;
|
||||
const node = jobqNodes.find((n) => n.id === id);
|
||||
const label = node ? node.payload.label : 'node ' + id;
|
||||
if (!(await themedConfirm({
|
||||
message: `cancel ${label}? a group root cancels the whole subtree; a mid-tree node cancels just that branch.`,
|
||||
danger: true, confirmLabel: '✕ cancel',
|
||||
}))) return;
|
||||
try {
|
||||
const r = await fetch('/api/rebuild-queue/' + id + '/cancel', { method: 'POST' });
|
||||
if (!r.ok) throw new Error('http ' + r.status);
|
||||
// No manual refresh: cancel flips node state, which fires
|
||||
// rebuild_queue_changed over SSE — the existing handler below
|
||||
// already calls jobqGraphEl.refresh() on that tick.
|
||||
} catch (err) {
|
||||
console.error('cancel failed', err);
|
||||
}
|
||||
});
|
||||
root.append(jobqGraphEl);
|
||||
}
|
||||
|
||||
// ─── running-rebuild live log ─────────────────────────────────────────────────
|
||||
|
|
@ -305,8 +306,8 @@ function updateRebuildCount() {
|
|||
function renderAll() {
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
// Rebuild-queue rendering + the count-pill/live-log it drives all happen
|
||||
// off <hive-jobq-graph>'s own hive-jobq-graph-update event (see
|
||||
// mountJobqGraph) — nothing to render here directly.
|
||||
// off JobqGraph's own onUpdate callback (see mountRebuildQueue) —
|
||||
// nothing to render here directly.
|
||||
}
|
||||
|
||||
// ─── BUILD L0GS tab ───────────────────────────────────────────────────────────
|
||||
|
|
@ -469,8 +470,8 @@ const SSE_HANDLERS = {
|
|||
// No `ev.queue` payload read anymore — this event still carries its own
|
||||
// queue snapshot on the wire (tabs.js/SW4RM still consumes it for the
|
||||
// swarm badges, untouched by this), this page just treats it as a
|
||||
// refetch trigger. <hive-jobq-graph> owns the actual fetch.
|
||||
if (jobqGraphEl) jobqGraphEl.refresh();
|
||||
// refetch trigger. `JobqGraph` owns the actual fetch.
|
||||
if (jobqGraphHandle) jobqGraphHandle.refresh();
|
||||
// Auto-refresh build log list when the queue changes and BUILD L0GS is active.
|
||||
if (buildTabs && buildTabs.active() === 'buildlogs') {
|
||||
if (buildRefreshTimer) clearTimeout(buildRefreshTimer);
|
||||
|
|
@ -501,9 +502,9 @@ async function refreshState() {
|
|||
async function init() {
|
||||
initServerWarnings();
|
||||
bindAsyncForms(() => refreshState());
|
||||
// Self-fetches on mount (<hive-jobq-graph>'s own connectedCallback) —
|
||||
// no explicit initial fetch needed here, unlike meta inputs below.
|
||||
mountJobqGraph();
|
||||
// Self-fetches on mount (JobqGraph's own effect) — no explicit initial
|
||||
// fetch needed here, unlike meta inputs below.
|
||||
mountRebuildQueue();
|
||||
|
||||
buildTabs = document.getElementById('builds-tabbar').configure({
|
||||
tabs: [
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ let jobqRollupState = [];
|
|||
// (applyRebuildQueueChanged below) — a payload-less push trigger by
|
||||
// design, confirmed with atlas on the jobq-deletion tracker: the event
|
||||
// carries no `queue` field this page reads, same "something changed,
|
||||
// go refetch" treatment builds.js already gives it for
|
||||
// <hive-jobq-graph>.refresh().
|
||||
// go refetch" treatment builds.js already gives its JobqGraph mount
|
||||
// handle's .refresh().
|
||||
// Best-effort: a failed fetch leaves the previous snapshot in place
|
||||
// rather than wiping the banner on a network blip.
|
||||
export async function refreshJobqRollup() {
|
||||
|
|
|
|||
|
|
@ -311,8 +311,8 @@ window.marked = marked;
|
|||
// handled on /core.html now (the SYST3M panels moved there).
|
||||
// rebuild_queue_changed: refreshes the SW4RM queue-summary banner
|
||||
// (see swarm.js) — a payload-less push trigger, same treatment
|
||||
// /builds.html gives it for <hive-jobq-graph>.refresh() (its own
|
||||
// separate subscription).
|
||||
// /builds.html gives it for its JobqGraph mount handle's .refresh()
|
||||
// (its own separate subscription).
|
||||
rebuild_queue_changed: applyRebuildQueueChanged,
|
||||
schedules_changed: applySchedulesChanged,
|
||||
capabilities_changed: applyCapabilitiesChanged,
|
||||
|
|
|
|||
Loading…
Reference in a new issue