From 40a0c2af786b68166807e765b408e84a3e09334c Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Thu, 7 May 2026 18:36:57 -0400 Subject: [PATCH] Dynamic resolved count, host search filter, lt-divider for UniFi section - db.py: add resolved_24h to get_status_summary() so each /api/status poll carries the fresh 24h resolved count - app.js: wire stat-resolved-val to update from summary.resolved_24h so the Resolved 24h card stays accurate after auto-refresh - index.html: add lt-toolbar/lt-search above host grid for quick client-side host filtering by name - links.html: replace custom unifi-section-header div with lt-divider - style.css: remove unused .unifi-section-header rules Co-Authored-By: Claude Sonnet 4.6 --- db.py | 7 +++++++ static/app.js | 2 ++ static/style.css | 17 ----------------- templates/index.html | 17 +++++++++++++++++ templates/links.html | 2 +- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/db.py b/db.py index 08873da..6b20a63 100644 --- a/db.py +++ b/db.py @@ -222,10 +222,17 @@ def get_status_summary() -> dict: WHERE resolved_at IS NULL GROUP BY severity""" ) counts = {r['severity']: r['cnt'] for r in cur.fetchall()} + cur.execute( + """SELECT COUNT(*) as cnt FROM network_events + WHERE resolved_at IS NOT NULL + AND resolved_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)""" + ) + resolved_24h = cur.fetchone()['cnt'] return { 'critical': counts.get('critical', 0), 'warning': counts.get('warning', 0), 'info': counts.get('info', 0), + 'resolved_24h': resolved_24h, } diff --git a/static/app.js b/static/app.js index d9e4e9d..c3d1497 100644 --- a/static/app.js +++ b/static/app.js @@ -92,8 +92,10 @@ function updateStatusBar(summary, lastCheck, daemonOk) { // Update stat cards const scCrit = document.getElementById('stat-critical-val'); const scWarn = document.getElementById('stat-warning-val'); + const scRes = document.getElementById('stat-resolved-val'); if (scCrit) scCrit.textContent = critCount; if (scWarn) scWarn.textContent = warnCount; + if (scRes && summary.resolved_24h != null) scRes.textContent = summary.resolved_24h; const statCritCard = document.getElementById('stat-critical'); if (statCritCard) statCritCard.classList.toggle('lt-stat-card--alert', critCount > 0); diff --git a/static/style.css b/static/style.css index 160403e..1b15d72 100644 --- a/static/style.css +++ b/static/style.css @@ -706,23 +706,6 @@ .poe-bar-warn { background: var(--amber); } .poe-bar-crit { background: var(--red); } -/* UniFi section divider */ -.unifi-section-header { - display: flex; - align-items: center; - gap: 12px; - margin: 24px 0 12px; - color: var(--cyan); - font-size: .75em; - letter-spacing: .1em; -} -.unifi-section-header::before, -.unifi-section-header::after { - content: ''; - flex: 1; - height: 1px; - background: linear-gradient(90deg, transparent, var(--cyan), transparent); -} .link-loading { padding: 20px; text-align: center; color: var(--text-muted); font-size: .8em; } diff --git a/templates/index.html b/templates/index.html index 7670724..d43be41 100644 --- a/templates/index.html +++ b/templates/index.html @@ -208,6 +208,14 @@ +
+
+ +
+
{% for name, host in snapshot.hosts.items() %} {% set suppressed = suppressions | selectattr('target_name', 'equalto', name) | list %} @@ -568,6 +576,15 @@ new MutationObserver(applyEventsFilter) .observe(document.getElementById('events-table-wrap'), { childList: true, subtree: true }); + // Host grid search filter + document.getElementById('host-search')?.addEventListener('input', function() { + const q = this.value.trim().toLowerCase(); + document.querySelectorAll('#host-grid .host-card').forEach(card => { + const name = (card.dataset.host || '').toLowerCase(); + card.style.display = (!q || name.includes(q)) ? '' : 'none'; + }); + }); + // Stat card clicks — filter events table by severity document.querySelectorAll('.lt-stat-card[data-stat-filter]').forEach(card => { card.addEventListener('click', () => { diff --git a/templates/links.html b/templates/links.html index f4749dc..1433fa8 100644 --- a/templates/links.html +++ b/templates/links.html @@ -358,7 +358,7 @@ function renderUnifiSwitches(unifiSwitches, dataUpdated) {
`; }).join(''); - return `
UNIFI SWITCH PORTS
${html}`; + return `
UNIFI SWITCH PORTS
${html}`; } // ── Panel collapse / expand ───────────────────────────────────────