// The rebuild queue tab of the dashboard (R3BU1LD QU3U3): pending and running // rebuilds as a `JobqGraph`. Not an entry point: tabs.js imports it, owns the // tab strip and the dashboard event stream, and calls the exports below. The // builds' output and history are not here: they are the `builds` Grafana board, // from the lines hive-c0re logs for each build. import { $ } from "./common.js"; import { themedConfirm } from "@hive/shared/modal.js"; import { h, render } from "preact"; import { JobqGraph } from "@hive/shared/jobq-graph.js"; // Kept in sync from JobqGraph's `onUpdate` callback (see renderRebuildQueue // below) — the component owns fetching GET /api/jobq/graph and renders the tree // itself; this flat array is only for `onCancel`, which names the node it // cancels in its confirm. let jobqNodes = []; let jobqGraphToken = 0; // ─── rebuild queue ──────────────────────────────────────────────────────────── // R3BU1LD QU3U3 is `JobqGraph` (Preact, rendered via plain `render(h(...))` // — no JSX pragma needed for either call, no mount wrapper: `render` is // already the re-render/diff entry point, per mara on review) directly — // no hand-rolled tree/roll-up rendering here anymore. The component owns // fetching GET /api/jobq/graph and its own refetch (bump `jobqGraphToken` // and render again); this page just reads its `onUpdate` callback to keep // `jobqNodes` (the flat array) in sync for `onCancel` below. // // 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. // Split from `renderRebuildQueue` below: `replaceChildren()` only // belongs on the *first* render, to clear the static "loading…" // placeholder `#rebuild-queue-section` starts with in `index.html` — // Preact doesn't know that placeholder is there, so a later render // call must NOT repeat `replaceChildren()` (it would wipe Preact's own // tracked children out from under its diffing rather than let it // update them minimally). function mountRebuildQueue() { const root = $("rebuild-queue-section"); if (!root) return; root.replaceChildren(); renderRebuildQueue(); } function renderRebuildQueue() { const root = $("rebuild-queue-section"); if (!root) return; render( h(JobqGraph, { endpoint: "/api/jobq/graph", cancellable: true, refreshToken: jobqGraphToken, onUpdate: (nodes) => { jobqNodes = nodes || []; }, 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 bumps jobqGraphToken and re-renders on that tick. } catch (err) { console.error("cancel failed", err); } }, }), root, ); } // ─── entry points for tabs.js ───────────────────────────────────────────────── // Everything below is called by tabs.js, which owns the tab strip, the // `/api/state` snapshot and the dashboard event stream. /** Mount the rebuild queue. Called once, after the tab strip exists. The queue * self-fetches on mount (JobqGraph's own effect). */ export function initBuilds() { mountRebuildQueue(); } /** `rebuild_queue_changed`: a payload-less refetch trigger — `JobqGraph` * owns the actual fetch. */ export function onRebuildQueueChanged() { jobqGraphToken += 1; renderRebuildQueue(); }