Add an alert history page and tie /health staleness to the poll interval
Lint / JS (eslint) (push) Successful in 6s
Lint / Python (flake8) (push) Successful in 1m56s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 2m3s
Test / Python Tests (pytest) (push) Successful in 1m0s
Lint / Python (flake8) (pull_request) Successful in 48s
Lint / JS (eslint) (pull_request) Successful in 6s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 2m7s
Test / Python Tests (pytest) (pull_request) Successful in 2m6s

/events lists every alert from the last 24 hours to 90 days, open or
cleared, with how long each lasted, failed checks and its ticket. Filters
(device, type, severity, state, range) live in the URL, so device pages
and tickets link straight to a device's history. A "Recurring" table
lists alerts that fired more than once on the same target. On phones the
rows become cards.

/api/events/history backs it (100 per page). The existing /api/events is
unchanged.

/health and the dashboard's monitor indicator now call the monitor
stalled after five missed polls (at least 10 minutes) instead of a fixed
20, and /health reports the monitor's age.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-10 20:29:09 -05:00
co-authored by Claude Opus 5.5
parent 9e2a2b987c
commit 9d561db703
11 changed files with 582 additions and 17 deletions
+29
View File
@@ -1393,3 +1393,32 @@ polygon.fp-led-off { fill: #6b6c71; }
.cs-label-card { break-inside: avoid; border-color: #000; color: #000; }
.lt-table, .lt-table td, .lt-table th { color: #000 !important; }
}
/* ── Alert history (/events) ───────────────────────────────────────── */
.ev-filters .lt-select { flex: 0 1 170px; }
.ev-type-chip { background: none; cursor: pointer; font: inherit; font-size: .82rem; }
.ev-type-chip:hover, .ev-type-chip:focus-visible { border-color: var(--accent-cyan); }
.ev-type-chip.active { border-color: var(--accent-cyan); background: var(--accent-cyan-dim); }
.ev-link { background: none; border: 0; padding: 0; color: inherit; font: inherit; cursor: pointer;
text-decoration: underline dotted; text-underline-offset: 3px; text-align: left; }
.ev-link:hover, .ev-link:focus-visible { color: var(--accent-cyan); }
.ev-table td { vertical-align: top; }
.ev-table .cs-port { display: block; }
.ev-pager { display: flex; gap: 12px; align-items: center; justify-content: center; margin-top: 12px; font-size: .85rem; }
.g-section-more { margin: 10px 12px 4px; font-size: .85rem; }
.ev-table .lt-badge, .ev-c-type { white-space: nowrap; }
/* Phones: each alert becomes a card instead of a squeezed table row */
@media (max-width: 760px) {
.ev-cards thead { display: none; }
.ev-cards, .ev-cards tbody { display: block; width: 100%; }
.ev-cards tr { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; padding: 10px 12px;
border-bottom: 1px solid var(--border-color); }
.ev-cards td { display: block; padding: 0; border: 0; max-width: none; white-space: normal; font-size: .82rem; }
.ev-cards td[data-label]::before { content: attr(data-label) " "; color: var(--text-muted); font-size: .72rem;
text-transform: uppercase; letter-spacing: .05em; }
.ev-cards .ev-c-target { flex: 1 1 100%; order: -1; font-size: .95rem; }
.ev-cards .ev-c-target .cs-port { display: inline; margin-left: 6px; }
.ev-cards .ev-c-desc { flex: 1 1 100%; color: var(--text-muted); white-space: normal; overflow: visible; }
.ev-cards .ev-c-action { flex: 1 1 100%; }
}