hive-c0re: hive warnings reach the operator as metrics and alerts, not a banner
The dashboard's top-of-page warning banner is gone. The warning registry
stays, because /health/ready, the swarm status view and the OTEL gauges
read it. What it feeds is now alerted on in Grafana:
- hyperhive.hive.warning{kind,level}: one series per raised warning, so an
alert names the cause. Raising a warning also logs one `hive warning
raised` line with the message.
- hyperhive.hive.nix_store.utilization: the used fraction of the nix
store's filesystem, probed in the gauge callback.
- Alerts: hh-hive-degraded on the existing degraded gauge, hh-hive-warning
per kind (disk excluded), hh-host-disk-high at 85% and
hh-host-disk-critical at 95%.
- pending_logins is now a registry warning, refreshed from the container
rescan, so it reaches the per-kind gauge.
Frontend: server_warnings leaves the state snapshot, and the dashboard,
flow and stats pages keep only the sticky chrome wrapper. The shared
hive-warn and WarnBanner components stay, since other views use them.
A module-eval case checks each alert's metric name against the exporter's
source, so a rename on either side fails the build.
This commit is contained in:
parent
013bbe5adc
commit
4dbcf6fa80
35 changed files with 468 additions and 322 deletions
|
|
@ -562,22 +562,12 @@ export function appendLinkified(parent, text, refs) {
|
|||
}
|
||||
}
|
||||
|
||||
// ─── server warnings banner ──────────────────────────────────────────
|
||||
// A generic top-of-page banner shown on every page (dashboard + the
|
||||
// stand-alone FL0W / ST4TS pages). The backend decides what to
|
||||
// warn about — `/api/state.server_warnings` is a list of
|
||||
// `{ kind, level, message }` — and this just renders it, coloured by
|
||||
// `level` (`warn` amber / `crit` red). Adding a new system warning is a
|
||||
// backend-only change. The bar is injected at the top of <body> so no
|
||||
// page needs to add markup.
|
||||
|
||||
// The sticky top region — one sticky container holding the warning
|
||||
// banner above the page's chrome (tab bar / page header), so the banner
|
||||
// stacks with the chrome instead of being overlaid by it (two separate
|
||||
// `top:0` stickies would otherwise collide). Built once by wrapping the
|
||||
// page's existing chrome element; pages without a chrome (none today)
|
||||
// get a banner-only sticky region at the top of <body>.
|
||||
function ensureStickyTop() {
|
||||
// ─── sticky top region ───────────────────────────────────────────────
|
||||
// One sticky container wrapping the page's chrome (tab bar / page
|
||||
// header), built once by wrapping the existing chrome element. A page
|
||||
// without a chrome (none today) gets an empty sticky region at the top of
|
||||
// <body>.
|
||||
export function initStickyTop() {
|
||||
let top = document.querySelector(".sticky-top");
|
||||
if (top) return top;
|
||||
top = document.createElement("div");
|
||||
|
|
@ -591,51 +581,3 @@ function ensureStickyTop() {
|
|||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
function ensureServerWarningsBar() {
|
||||
let bar = document.getElementById("server-warnings");
|
||||
if (!bar) {
|
||||
bar = document.createElement("div");
|
||||
bar.id = "server-warnings";
|
||||
bar.className = "server-warnings";
|
||||
bar.setAttribute("role", "alert");
|
||||
bar.hidden = true;
|
||||
ensureStickyTop().prepend(bar);
|
||||
}
|
||||
return bar;
|
||||
}
|
||||
|
||||
/// Render a `server_warnings` list (from /api/state) into the banner.
|
||||
/// Empty / missing → the bar hides itself.
|
||||
export function renderServerWarnings(warnings) {
|
||||
const bar = ensureServerWarningsBar();
|
||||
bar.replaceChildren();
|
||||
if (!Array.isArray(warnings) || warnings.length === 0) {
|
||||
bar.hidden = true;
|
||||
return;
|
||||
}
|
||||
for (const w of warnings) {
|
||||
const row = el("div", {
|
||||
class:
|
||||
"server-warn server-warn-" +
|
||||
(w && w.level === "crit" ? "crit" : "warn"),
|
||||
});
|
||||
appendText(row, "⚠ " + ((w && w.message) || ""));
|
||||
bar.append(row);
|
||||
}
|
||||
bar.hidden = false;
|
||||
}
|
||||
|
||||
/// One-shot init for pages that don't otherwise poll /api/state: ensure
|
||||
/// the bar exists, fetch the snapshot once, render. The dashboard (which
|
||||
/// already polls /api/state) calls `renderServerWarnings` directly for
|
||||
/// live updates instead.
|
||||
export function initServerWarnings() {
|
||||
ensureServerWarningsBar();
|
||||
fetch("/api/state")
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((s) => renderServerWarnings(s && s.server_warnings))
|
||||
.catch(() => {
|
||||
/* non-fatal: no banner if the snapshot is unreachable */
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue