Skip to content

Commit 46a9533

Browse files
authored
feat(kuma-config): declarative public status pages (#43)
* feat(kuma-config): declarative public status pages statusPages in kuma.yaml creates/reconciles Kuma status pages over the same Socket.IO API (getStatusPage/addStatusPage/saveStatusPage — identical signatures in 2.3.2 and 2.5.0). Page layout is authoritative; undeclared pages are untouched. Declares /status/zfnd with the seeder and web-page monitors (prober excluded). * feat(kuma-config): adopt existing Ecosystem Dashboard status page Prod already has /status/dashboard (created manually, missing the Z.CASH monitor); declare it instead of adding a second page so its URL and the Entry Page wiring survive. * fix(kuma-config): only treat Kuma's not-found as a missing status page Rethrow other getStatusPage errors instead of proceeding to create, and honor the round-trip contract for showTags/showCertificateExpiry when the YAML omits them.
1 parent 7e94767 commit 46a9533

3 files changed

Lines changed: 101 additions & 1 deletion

File tree

kuma-config/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ To operate it, the `prod` GitHub environment provides these non-secret
8888
Secrets read from Secret Manager (never GitHub): `UPTIME_KUMA_ADMIN_PASSWORD`,
8989
`SLACK_WEBHOOK_URL`.
9090

91+
## Status pages
92+
93+
`statusPages:` declares public, no-login views served at `/status/<slug>`
94+
(e.g. `https://status.zfnd.org/status/zfnd`). Unlike monitors, a page's layout
95+
is **authoritative**: on apply, its groups/monitors are replaced by the declared
96+
list. Pages not declared in the file are never touched. Monitors are referenced
97+
by name; unknown names are skipped with a warning (e.g. the prober before it
98+
exists in that instance).
99+
100+
The `/dashboard` admin UI always requires login — publishing a status page is
101+
the supported way to expose state publicly, and only the monitors you list.
102+
91103
## Adding channels later
92104

93105
Add another entry under `notifications:` (e.g. PagerDuty/email) with its own

kuma-config/apply.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ async function main() {
244244
}
245245

246246
// 4. Monitors
247+
const monitorIds = {}; // name -> id, for status page wiring below
247248
for (const m of cfg.monitors || []) {
248249
// Skip monitors whose ${VAR} placeholders weren't resolved (e.g. the
249250
// prober monitor before PROBER_BASE_URL is set) so we never create a
@@ -304,6 +305,7 @@ async function main() {
304305
monitorID = res.monitorID;
305306
log(`created monitor "${m.name}" (id=${monitorID})`);
306307
}
308+
monitorIds[m.name] = monitorID;
307309

308310
if (DRY_RUN) continue;
309311

@@ -323,6 +325,67 @@ async function main() {
323325
}
324326
}
325327

328+
// 5. Status pages (public, no-login views at /status/<slug>). Unlike the
329+
// monitor reconcile, a page's layout is authoritative: saveStatusPage
330+
// replaces its groups/monitors with the declared list. Pages not declared
331+
// here are never touched. Events verified against 2.3.2 and 2.5.0:
332+
// getStatusPage / addStatusPage / saveStatusPage have identical signatures.
333+
for (const sp of cfg.statusPages || []) {
334+
// Kuma answers a missing slug with the literal "No slug?"; anything
335+
// else (auth, transport) is a real failure and must abort the apply.
336+
const existing = await emit("getStatusPage", sp.slug).catch((e) => {
337+
if (e.message === "No slug?") return null;
338+
throw e;
339+
});
340+
if (DRY_RUN) {
341+
log(
342+
`[dry-run] ${existing ? "reconcile" : "create"} status page "${sp.slug}" (${(sp.groups || []).length} groups)`,
343+
);
344+
continue;
345+
}
346+
if (!existing) {
347+
await emit("addStatusPage", sp.title, sp.slug);
348+
log(`created status page "${sp.slug}"`);
349+
}
350+
const full = (await emit("getStatusPage", sp.slug)).config;
351+
const groupList = (sp.groups || []).map((g) => ({
352+
name: g.name,
353+
monitorList: (g.monitors || [])
354+
.filter((name) => {
355+
if (monitorIds[name] == null) {
356+
log(
357+
` ! status page "${sp.slug}": unknown monitor "${name}" — skip`,
358+
);
359+
return false;
360+
}
361+
return true;
362+
})
363+
.map((name) => ({ id: monitorIds[name] })),
364+
}));
365+
// Round-trip the stored config so undeclared fields survive; icon goes
366+
// through the imgDataUrl param (non-data: values are stored verbatim).
367+
await emit(
368+
"saveStatusPage",
369+
sp.slug,
370+
{
371+
...full,
372+
slug: sp.slug,
373+
title: sp.title,
374+
description: sp.description ?? full.description ?? null,
375+
theme: sp.theme || full.theme || "auto",
376+
showTags: sp.showTags ?? full.showTags ?? false,
377+
showPoweredBy: sp.showPoweredBy ?? full.showPoweredBy ?? false,
378+
showCertificateExpiry:
379+
sp.showCertificateExpiry ??
380+
full.showCertificateExpiry ??
381+
false,
382+
},
383+
full.icon || "",
384+
groupList,
385+
);
386+
log(`reconciled status page "${sp.slug}" (${groupList.length} groups)`);
387+
}
388+
326389
log(DRY_RUN ? "dry-run complete" : "apply complete");
327390
socket.close();
328391
}

kuma-config/kuma.yaml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,29 @@ monitors:
161161
expiryNotification: true
162162
notifications: [Slack]
163163
tags:
164-
- { name: Org, value: ZFND }
164+
- { name: Org, value: ZFND }
165+
166+
# Public status pages, served without login at /status/<slug>. A page's layout
167+
# is authoritative: on apply, its groups/monitors are replaced by what is
168+
# declared here (pages not listed are never touched). The prober monitor is
169+
# intentionally not exposed — it's an internal signal.
170+
# Adopts the pre-existing "Ecosystem Dashboard" page (slug `dashboard`) rather
171+
# than creating a second one, so its URL and the Entry Page wiring survive.
172+
statusPages:
173+
- slug: dashboard
174+
title: Ecosystem Dashboard
175+
description: Live status of Zcash Foundation public services and DNS seeders.
176+
showTags: true
177+
showCertificateExpiry: true
178+
groups:
179+
- name: Seeders
180+
monitors:
181+
- ZFND Mainnet
182+
- ZFND Testnet
183+
- ZODL Mainnet
184+
- ZODL Testnet
185+
- Str4d Mainnet
186+
- name: Web Pages
187+
monitors:
188+
- ZFND Web Page
189+
- Z.CASH Web Page

0 commit comments

Comments
 (0)