Add a public reachability status page
All checks were successful
Build and Deploy to Production / test (push) Successful in 18s
Build and Deploy to Production / build (push) Successful in 49s
Build and Deploy to Production / deploy (push) Successful in 33s

asxpio now probes public hosts tied to each datacenter once a minute
and stores 5-minute rows (bingo_monitor_* tables, granted to the
read-only bingo role). Show them: /:locale/status lists every monitored
site with its current state and a 24-hour bar, /status.json has the
same data, and monitored datacenter pages get a small block.

A site is down only when nothing answered for two buckets. Everything
reads as unknown when the worker is stale or fewer than half of the
control hosts in Russia answer, since probes run from Finland and
filtering can look like an outage. The page says plainly that this is
not an incident record.

Refresh test/schema.sql for asxpio's migration 017.

Assisted-by: Claude:opus-5-5
This commit is contained in:
Sergei Poljanski 2026-10-18 10:33:52 +04:00
commit 1a5506c77d
Signed by: asxpi
GPG key ID: 4F8851660FA4121B
10 changed files with 414 additions and 9 deletions

View file

@ -48,18 +48,18 @@
<style>
:root {
--bg: #f4f1ea; --card: #fffdf8; --ink: #1d1b18; --muted: #6f6a60; --line: #d9d3c6;
--hit: #c8361d; --hit-bg: #fbe3dc; --win: #e7b416; --on-win: #1d1b18;
--hit: #c8361d; --hit-bg: #fbe3dc; --win: #e7b416; --on-win: #1d1b18; --up: #2f7d32;
--shade: rgb(0 0 0 / .62); --on-photo: #fff; --on-photo-muted: rgb(255 255 255 / .75);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #121212; --card: #1c1b19; --ink: #ece7dc; --muted: #9a9384; --line: #34312b;
--hit: #ff6a4d; --hit-bg: #3a1d16; --win: #f0c43a;
--hit: #ff6a4d; --hit-bg: #3a1d16; --win: #f0c43a; --up: #5cc066;
}
}
:root[data-theme="dark"] {
--bg: #121212; --card: #1c1b19; --ink: #ece7dc; --muted: #9a9384; --line: #34312b;
--hit: #ff6a4d; --hit-bg: #3a1d16; --win: #f0c43a;
--hit: #ff6a4d; --hit-bg: #3a1d16; --win: #f0c43a; --up: #5cc066;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--ink); font: 15px/1.4 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
@ -149,6 +149,25 @@
.dc-page ul { padding-left: 18px; }
.pager { display: flex; justify-content: space-between; gap: 12px; margin-top: 32px; padding-top: 12px; border-top: 1px solid var(--line); }
dialog button { font: inherit; padding: 6px 14px; border-radius: 6px; border: 1px solid var(--line); background: var(--bg); color: var(--ink); cursor: pointer; }
/* Reachability (status page and datacenter pages) */
.st { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.st::before { content: ""; width: 10px; height: 10px; border-radius: 50%; background: var(--line); flex: none; }
.st.up::before { background: var(--up); }
.st.degraded::before { background: var(--win); }
.st.down::before { background: var(--hit); }
.st.down { color: var(--hit); font-weight: 600; }
.status-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.status-table th { text-align: left; font-weight: 600; color: var(--muted); font-size: 12px; padding: 6px 8px 6px 0; border-bottom: 1px solid var(--line); }
.status-table td { padding: 8px 8px 8px 0; border-bottom: 1px solid var(--line); vertical-align: middle; }
.status-table .num { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }
.status-table .city { display: block; color: var(--muted); font-size: 12px; }
.bars { display: grid; grid-template-columns: repeat(24, 1fr); gap: 2px; min-width: 120px; height: 18px; }
.bars i { border-radius: 2px; background: var(--line); }
.bars i.up { background: var(--up); }
.bars i.degraded { background: var(--win); }
.bars i.down { background: var(--hit); }
.status-banner { padding: 10px 14px; border-radius: 8px; background: var(--hit-bg); margin: 0 0 16px; max-width: 65ch; }
.unmonitored { color: var(--muted); font-size: 13px; max-width: 75ch; }
footer { margin-top: 32px; color: var(--muted); font-size: 13px; }
footer p { margin: 0 0 4px; }
@media (max-width: 560px) {
@ -156,6 +175,7 @@
.cell .sub, .cell .n, .cell .tag { display: none; }
.letters, .grid { gap: 4px; }
.log li { grid-template-columns: 84px 24px 1fr; }
.status-table .hide-sm { display: none; }
}
</style>
</head>