Watch
0
0
Fork
You've already forked hyperhive
0

dashboard: SCH3DUL3S is a read-only list with a removal banner

The tab lists the rows left in the hive's store so the operator can
recreate them with `swarmctl schedule add`: no create row, edit form,
fire/pause/cancel buttons or schedules_changed subscription. The next
column shows the stored next fire time as an absolute UTC timestamp (the
value for --at) instead of a countdown. A warning banner says the page is
slated for removal and points at swarmctl. The approvals view drops the
schedule_prompt card and the approval_added subscription.
This commit is contained in:
atlas 2026-10-04 21:23:18 +02:00
commit a4a3f81d5c
8 changed files with 68 additions and 1500 deletions

View file

@ -17,7 +17,7 @@ import { el } from "@hive/shared/dom.js";
import { bindAsyncForms } from "@hive/shared/forms.js";
import { createTabStrip } from "@hive/shared/tabs.js";
import { containersState, syncContainersFromSnapshot } from "./state.js";
import { fmtAgo, fmtDuration } from "./util.js";
import { fmtAgo } from "./util.js";
import {
applyCapabilitiesChanged,
applyToolGroupsChanged,
@ -25,18 +25,13 @@ import {
fetchAndRenderToolGroups,
initPermissions,
} from "./permissions.js";
import {
applySchedulesChanged,
refreshSchedules,
activeScheduleCount,
} from "./schedules.js";
import { refreshSchedules, activeScheduleCount } from "./schedules.js";
import {
initCall,
refreshOperatorInbox,
operatorInboxAppendFromEvent,
operatorInboxCount,
syncApprovalsFromSnapshot,
applyApprovalAdded,
applyApprovalResolved,
renderApprovals,
activeApprovalCount,
@ -95,11 +90,9 @@ window.marked = marked;
// opts out via `data-no-refresh` (its mutation arrives via an SSE event).
bindAsyncForms(() => refreshState());
// One ticker feeds two live displays that would otherwise only
// update on the next SSE-triggered re-render: approval request-age
// chips (`.approval-ts`, flips `.stale` at the 1h mark) and schedule
// next-fire countdowns (`.sched-due`, stamped with `data-due-at` by
// the renderers so this loop can refresh them without a full re-render).
// Refreshes approval request-age chips (`.approval-ts`, flips `.stale`
// at the 1h mark), which would otherwise only update on the next
// SSE-triggered re-render.
setInterval(() => {
const now = Math.floor(Date.now() / 1000);
document
@ -111,13 +104,6 @@ window.marked = marked;
node.textContent = "requested " + fmtAgo(requestedAt);
node.classList.toggle("stale", ageSec >= 3600);
});
document.querySelectorAll(".sched-due[data-due-at]").forEach((node) => {
const dueAt = Number(node.getAttribute("data-due-at"));
if (!Number.isFinite(dueAt)) return;
const dueIn = dueAt - now;
node.textContent =
dueIn <= 0 ? "overdue " + fmtAgo(dueAt) : fmtDuration(dueIn);
});
}, 1000);
// ─── state polling ──────────────────────────────────────────────────────
@ -284,7 +270,6 @@ window.marked = marked;
// and filter client-side — the dashboard ignores broker traffic
// and the inbox ignores mutation events.
const MUTATION_HANDLERS = {
approval_added: applyApprovalAdded,
approval_resolved: applyApprovalResolved,
transient_set: applyTransientSet,
transient_cleared: applyTransientCleared,
@ -295,7 +280,6 @@ window.marked = marked;
// /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,
tool_groups_changed: applyToolGroupsChanged,
};
@ -309,15 +293,15 @@ window.marked = marked;
//
// kinds= matches MUTATION_HANDLERS below verbatim, plus `sent`
// (checked separately, just above, for the operator inbox) —
// narrows this from all 15 wire kinds down to the 11 this page
// narrows this from all 13 wire kinds down to the 9 this page
// actually acts on. This page was one of 4 unfiltered
// `/api/dashboard/stream` subscribers before this (subscription
// discipline, part 1 of the dashboard-event-stream-split issue).
const es = openStream(
"/api/dashboard/stream?kinds=sent,approval_added,approval_resolved," +
"/api/dashboard/stream?kinds=sent,approval_resolved," +
"transient_set,transient_cleared," +
"container_state_changed,container_removed,rebuild_queue_changed," +
"schedules_changed,capabilities_changed,tool_groups_changed",
"capabilities_changed,tool_groups_changed",
);
es.onmessage = (e) => {
let ev;
@ -436,8 +420,7 @@ window.marked = marked;
const rawTitle = document.title.replace(/^\(\d+\) /, "");
document.title = callCount > 0 ? `(${callCount}) ${rawTitle}` : rawTitle;
// SCH3DUL3S — count of schedules with at least one still-active
// target (whole-schedule cancellation or all-targets-cancelled
// means "not waiting on the worker"; those don't pull attention).
// target: the ones left to recreate with `swarmctl schedule`.
setTabCount("schedules", activeScheduleCount());
}
// Poll the state stores on a 1s tick to keep the pill counts in