build agents only from internal/meta; delete the hive's local meta flake
The hive no longer renders, locks or commits a meta flake. A rebuild's MetaSync node picks the internal/meta commit and records it with the hyperhive and config pins it holds; Prebuild, Swap and Create build from it, and a head that does not pin the agent's current config fails the build naming the agent. hive-priv's meta_rev is required. Removed: meta.rs sync/lock/render, the manual-rebuild relock, the boot sweep's hyperhive bump (MetaLock), the update-meta-inputs form and its panel, MetaWindow, the hive's hyperhive_rev setting. Kept: agent_set::refresh holds what sync_agents also did (topology.json, the gateway's agent-socket map and nginx include), called from spawn, destroy, rebuild and the migration. The boot reconcile and the stale-start upgrade compare the agent's marker with the hyperhive pin of internal/meta; the dashboard's deployed chip reads the config marker a successful build writes.
This commit is contained in:
parent
cea4827918
commit
4aa11b3a94
1 changed files with 1042 additions and 3793 deletions
|
|
@ -1,9 +1,8 @@
|
|||
// The build lifecycle tabs of the dashboard — rebuild queue, live build
|
||||
// log, meta inputs, and build log history. Not an entry point: tabs.js
|
||||
// log, and build log history. Not an entry point: tabs.js
|
||||
// imports it, owns the tab strip and the dashboard event stream, and calls
|
||||
// the exports below. Three tabs, the panes' ids matching the tab ids:
|
||||
// the exports below. Two tabs, the panes' ids matching the tab ids:
|
||||
// - rebuild (R3BU1LD QU3U3): pending + running rebuilds + live build log
|
||||
// - meta (M3T4 1NPUTS): nix flake input selector + meta-update
|
||||
// - buildlogs (BUILD L0GS): all-agents build log history, lazy-loaded
|
||||
// the first time the tab is shown
|
||||
|
||||
|
|
@ -11,12 +10,11 @@ import { $, fmtAgeSecs, openBuildLogStream } from "./common.js";
|
|||
import { el } from "@hive/shared/dom.js";
|
||||
import { themedConfirm } from "@hive/shared/modal.js";
|
||||
import { h, render } from "preact";
|
||||
import { fmtAgo, fmtDuration, truncate } from "./util.js";
|
||||
import { fmtDuration } from "./util.js";
|
||||
import { JobqGraph } from "@hive/shared/jobq-graph.js";
|
||||
|
||||
// ─── derived state ───────────────────────────────────────────────────────────
|
||||
let metaInputsState = [];
|
||||
let metaUpdateRunning = false;
|
||||
|
||||
// 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
|
||||
|
|
@ -24,133 +22,6 @@ let metaUpdateRunning = false;
|
|||
let jobqNodes = [];
|
||||
let jobqGraphToken = 0;
|
||||
|
||||
function syncFromSnapshot(s) {
|
||||
metaInputsState = (s.meta_inputs || []).slice();
|
||||
metaUpdateRunning = !!s.meta_update_running;
|
||||
}
|
||||
|
||||
// ─── meta inputs ─────────────────────────────────────────────────────────────
|
||||
function renderMetaInputs(s) {
|
||||
const root = $("meta-inputs-section");
|
||||
if (!root) return;
|
||||
// Snapshot ticked checkboxes before wiping so a concurrent
|
||||
// MetaInputsChanged doesn't silently clear a pending selection.
|
||||
const checkedInputs = new Set(
|
||||
Array.from(
|
||||
root.querySelectorAll('input[type="checkbox"][data-meta-input]:checked'),
|
||||
).map((cb) => cb.dataset.metaInput),
|
||||
);
|
||||
root.replaceChildren();
|
||||
const inputs = s.meta_inputs || [];
|
||||
if (!inputs.length) {
|
||||
root.append(el("p", { class: "empty" }, "meta repo not seeded yet"));
|
||||
return;
|
||||
}
|
||||
if (metaUpdateRunning) {
|
||||
root.append(
|
||||
el(
|
||||
"p",
|
||||
{ class: "meta-update-running" },
|
||||
"⏳ meta-update running — flake lock bump + affected agents rebuilding. " +
|
||||
"watch the agent cards for per-rebuild progress.",
|
||||
),
|
||||
);
|
||||
}
|
||||
const f = el("form", {
|
||||
method: "POST",
|
||||
action: "/api/meta-update",
|
||||
class: "meta-inputs-form",
|
||||
"data-async": "",
|
||||
"data-no-refresh": "",
|
||||
"data-confirm":
|
||||
"update selected meta flake inputs + rebuild affected agents?",
|
||||
});
|
||||
const bulk = el("div", { class: "meta-inputs-bulk" });
|
||||
const selAll = el(
|
||||
"button",
|
||||
{ type: "button", class: "meta-bulk-btn" },
|
||||
"select all",
|
||||
);
|
||||
const selNone = el(
|
||||
"button",
|
||||
{ type: "button", class: "meta-bulk-btn" },
|
||||
"select none",
|
||||
);
|
||||
bulk.append("bulk: ", selAll, " ", selNone);
|
||||
f.append(bulk);
|
||||
const ul = el("ul", { class: "meta-inputs" });
|
||||
for (const inp of inputs) {
|
||||
const depth = (inp.name.match(/\//g) || []).length;
|
||||
const leaf = inp.name.slice(inp.name.lastIndexOf("/") + 1);
|
||||
const li = el("li");
|
||||
if (depth > 0) li.style.marginLeft = depth * 1.3 + "em";
|
||||
const id = "meta-input-" + inp.name.replace(/[^a-z0-9-]/gi, "_");
|
||||
const cb = el("input", {
|
||||
type: "checkbox",
|
||||
name: "meta_input_" + inp.name,
|
||||
id,
|
||||
value: inp.name,
|
||||
"data-meta-input": inp.name,
|
||||
});
|
||||
if (checkedInputs.has(inp.name)) cb.checked = true;
|
||||
const label = el("label", { for: id, title: inp.name });
|
||||
label.append(cb);
|
||||
if (depth > 0) label.append(el("span", { class: "meta-input-twig" }, "└ "));
|
||||
label.append(
|
||||
el("span", { class: "meta-input-name" }, leaf),
|
||||
" ",
|
||||
el("code", { class: "meta-input-rev" }, inp.rev.slice(0, 12)),
|
||||
" ",
|
||||
el("span", { class: "meta-input-ts" }, fmtAgo(inp.last_modified)),
|
||||
);
|
||||
if (inp.url) {
|
||||
label.append(
|
||||
" ",
|
||||
el(
|
||||
"span",
|
||||
{ class: "meta-input-url", title: inp.url },
|
||||
"· " + truncate(inp.url, 48),
|
||||
),
|
||||
);
|
||||
}
|
||||
li.append(label);
|
||||
ul.append(li);
|
||||
}
|
||||
f.append(ul);
|
||||
const hidden = el("input", { type: "hidden", name: "inputs", value: "" });
|
||||
f.append(hidden);
|
||||
const btn = el(
|
||||
"button",
|
||||
{
|
||||
type: "submit",
|
||||
class: "btn btn-meta-update",
|
||||
disabled: "",
|
||||
},
|
||||
metaUpdateRunning ? "⏳ UPD4T1NG…" : "◆ UPD4TE & R3BU1LD",
|
||||
);
|
||||
f.append(btn);
|
||||
function refreshDisabled() {
|
||||
const any = f.querySelectorAll("input[data-meta-input]:checked").length > 0;
|
||||
if (any && !metaUpdateRunning) btn.removeAttribute("disabled");
|
||||
else btn.setAttribute("disabled", "");
|
||||
}
|
||||
f.addEventListener("change", refreshDisabled);
|
||||
function setAllChecked(val) {
|
||||
for (const b of f.querySelectorAll("input[data-meta-input]"))
|
||||
b.checked = val;
|
||||
refreshDisabled();
|
||||
}
|
||||
selAll.addEventListener("click", () => setAllChecked(true));
|
||||
selNone.addEventListener("click", () => setAllChecked(false));
|
||||
f.addEventListener("submit", () => {
|
||||
const selected = Array.from(
|
||||
f.querySelectorAll("input[data-meta-input]:checked"),
|
||||
).map((b) => b.dataset.metaInput);
|
||||
hidden.value = selected.join(",");
|
||||
});
|
||||
root.append(f);
|
||||
}
|
||||
|
||||
// ─── rebuild queue ────────────────────────────────────────────────────────────
|
||||
// R3BU1LD QU3U3 is `JobqGraph` (Preact, rendered via plain `render(h(...))`
|
||||
// — no JSX pragma needed for either call, no mount wrapper: `render` is
|
||||
|
|
@ -594,20 +465,13 @@ async function fetchBuild() {
|
|||
|
||||
/** Mount the rebuild queue and wire the build-log refresh button. Called
|
||||
* once, after the tab strip exists. The queue self-fetches on mount
|
||||
* (JobqGraph's own effect), unlike the meta inputs, which wait for the
|
||||
* snapshot. */
|
||||
* (JobqGraph's own effect). */
|
||||
export function initBuilds() {
|
||||
const refresh = $("build-refresh");
|
||||
if (refresh) refresh.addEventListener("click", fetchBuild);
|
||||
mountRebuildQueue();
|
||||
}
|
||||
|
||||
/** The meta inputs, from an `/api/state` snapshot. */
|
||||
export function syncBuildsFromSnapshot(s) {
|
||||
syncFromSnapshot(s);
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
}
|
||||
|
||||
/** A tab was shown: BUILD L0GS loads its list the first time. */
|
||||
export function onBuildsTabShown(tab) {
|
||||
if (tab === "buildlogs" && !buildLogsLoaded) {
|
||||
|
|
@ -629,13 +493,3 @@ export function onRebuildQueueChanged() {
|
|||
buildRefreshTimer = setTimeout(fetchBuild, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
export function onMetaInputsChanged(ev) {
|
||||
metaInputsState = (ev.inputs || []).slice();
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
}
|
||||
|
||||
export function onMetaUpdateRunning(ev) {
|
||||
metaUpdateRunning = !!ev.running;
|
||||
renderMetaInputs({ meta_inputs: metaInputsState });
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue