diff --git a/static/app.js b/static/app.js index 46fcd91..55ff901 100644 --- a/static/app.js +++ b/static/app.js @@ -86,11 +86,13 @@ function updateStatusBar(summary, lastCheck, daemonOk) { if (!staleBanner) { staleBanner = document.createElement('div'); staleBanner.id = 'stale-banner'; - staleBanner.className = 'stale-banner'; + staleBanner.className = 'lt-alert lt-alert--warning'; + staleBanner.innerHTML = '
'; document.querySelector('.lt-main').prepend(staleBanner); } const mins = Math.floor(checkAge / 60); - staleBanner.textContent = `⚠ Monitoring data is stale — last check was ${mins} minute${mins !== 1 ? 's' : ''} ago. The monitor daemon may be down.`; + staleBanner.querySelector('.lt-alert-msg').textContent = + `Monitoring data is stale — last check was ${mins} minute${mins !== 1 ? 's' : ''} ago. The monitor daemon may be down.`; staleBanner.style.display = ''; } else if (staleBanner) { staleBanner.style.display = 'none'; @@ -138,6 +140,11 @@ function updateTopology(hosts) { badge.className = `topo-badge topo-badge-${host.status}`; badge.textContent = host.status; } + + // Animate the 10G drop-wire red+dashed when host is down + document.querySelectorAll(`.topo-v2-wire-10g[data-host="${CSS.escape(name)}"]`).forEach(wire => { + wire.classList.toggle('wire-down', host.status === 'down'); + }); }); } diff --git a/static/style.css b/static/style.css index 58f88d9..910b9ad 100644 --- a/static/style.css +++ b/static/style.css @@ -132,6 +132,7 @@ .badge-neutral { color: var(--text-muted); border-color: var(--text-muted); } .badge-resolved { color: var(--text-muted); border-color: var(--border-color); text-decoration: line-through; } .badge-suppressed { font-size: .9em; padding: 0; border: none; color: var(--text-muted); } +.badge-purple { color: var(--accent-purple); border-color: var(--accent-purple); } /* ── Table row state colors ───────────────────────────────────────── */ .lt-table tr.row-critical td { background: rgba(255,45,85,.04); } @@ -179,15 +180,7 @@ .last-check { font-size: .72em; color: var(--text-muted); } /* ── Stale monitoring banner ──────────────────────────────────────── */ -.stale-banner { - background: var(--amber-dim); - border: 1px solid var(--amber); - border-left: 4px solid var(--amber); - color: var(--amber); - padding: 10px 16px; - margin: 0 0 14px; - font-size: .88em; -} +/* .stale-banner replaced by lt-alert--warning */ /* ── Error / empty state containers ───────────────────────────────── */ .error-state { @@ -397,9 +390,17 @@ height: 28px; align-items: flex-start; } -.topo-v2-wire-10g { width: 2px; height: 100%; background: var(--green); opacity: .55; } +.topo-v2-wire-10g { width: 2px; height: 100%; background: var(--green); opacity: .55; transition: background .3s, opacity .3s; } .topo-v2-wire-1g { width: 0; height: 100%; border-left: 2px dashed var(--amber); opacity: .45; } +@keyframes wire-dash-anim { to { background-position: 0 -20px; } } +.topo-v2-wire-10g.wire-down { + background: repeating-linear-gradient(to bottom, var(--red) 0 6px, transparent 6px 10px) !important; + background-size: 2px 10px !important; + opacity: .9 !important; + animation: wire-dash-anim .7s linear infinite; +} + /* Bus rails */ .topo-bus-section { width: 100%; @@ -557,14 +558,11 @@ .counter-zero { color: var(--green); } .counter-nonzero { color: var(--red); text-shadow: var(--glow-red); } -/* Traffic bars */ +/* Traffic bars — use lt-progress from base.css */ .traffic-section { margin-top: 8px; padding-top: 8px; border-top: 1px solid rgba(255,107,0,.08); } .traffic-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; } .traffic-label { font-size: .62em; color: var(--text-muted); width: 20px; text-transform: uppercase; letter-spacing: .04em; flex-shrink: 0; } -.traffic-bar-track{ flex: 1; height: 5px; background: var(--bg-primary); border: 1px solid rgba(255,107,0,.15); position: relative; overflow: hidden; } -.traffic-bar-fill { height: 100%; position: absolute; left: 0; top: 0; transition: width .4s; } -.traffic-tx { background: var(--cyan); box-shadow: 0 0 3px rgba(0,212,255,.4); } -.traffic-rx { background: var(--green); box-shadow: 0 0 3px rgba(0,255,136,.4); } +.traffic-row .lt-progress { flex: 1; height: 5px; } .traffic-value { font-size: .7em; color: var(--text-dim); width: 68px; text-align: right; flex-shrink: 0; } /* SFP / optical panel */ @@ -665,7 +663,7 @@ .link-loading { padding: 20px; text-align: center; color: var(--text-muted); font-size: .8em; } .link-loading::after { content: ' ...'; animation: blink 1s step-end infinite; } .link-no-data { padding: 14px; color: var(--text-muted); font-size: .78em; text-align: center; } -.stale-banner { margin-bottom: 12px; } +.lt-alert { margin-bottom: 12px; } /* ── Inspector page ───────────────────────────────────────────────── */ .inspector-layout { diff --git a/templates/index.html b/templates/index.html index 4fd9d19..27dd4da 100644 --- a/templates/index.html +++ b/templates/index.html @@ -138,8 +138,8 @@
-
-
+
+
85) return 'lt-progress--red'; + return isTx ? '' : 'lt-progress--cyan'; +} + function fmtSpeed(mbps) { if (mbps === null || mbps === undefined) return '–'; if (mbps >= 1000) return (mbps/1000).toFixed(0) + ' Gbps'; @@ -226,12 +231,12 @@ function renderIfaceCard(ifaceName, d) {
TX -
+
${fmtRate(d.tx_bytes_rate)}
RX -
+
${fmtRate(d.rx_bytes_rate)}
@@ -289,12 +294,12 @@ function renderPortCard(portName, d) {
TX -
+
${fmtRate(d.tx_bytes_rate)}
RX -
+
${fmtRate(d.rx_bytes_rate)}
@@ -477,10 +482,12 @@ function checkLinksStale(updatedStr) { if (!banner) { banner = document.createElement('div'); banner.id = 'links-stale-banner'; - banner.className = 'stale-banner'; + banner.className = 'lt-alert lt-alert--warning'; + banner.innerHTML = '
'; document.getElementById('links-container').prepend(banner); } - banner.textContent = `⚠ Link data may be stale — last updated ${Math.floor(age/60)}m ago.`; + banner.querySelector('.lt-alert-msg').textContent = + `Link data may be stale — last updated ${Math.floor(age/60)}m ago.`; banner.style.display = ''; } else if (banner) { banner.style.display = 'none'; diff --git a/templates/suppressions.html b/templates/suppressions.html index a77df40..1666936 100644 --- a/templates/suppressions.html +++ b/templates/suppressions.html @@ -95,7 +95,8 @@ {% for s in active %} - {{ s.target_type }} + {%- set _sup_badge = {'host':'badge-warning','interface':'badge-info','unifi_device':'badge-purple','all':'badge-critical'} -%} + {{ s.target_type }} {{ s.target_name or 'all' }} {{ s.target_detail or '–' }} {{ s.reason }} @@ -214,9 +215,10 @@ return; } if (badge) badge.textContent = rows.length; + const SUP_BADGE = {host:'badge-warning', interface:'badge-info', unifi_device:'badge-purple', all:'badge-critical'}; const tbody = rows.map(s => ` - ${lt.escHtml(s.target_type)} + ${lt.escHtml(s.target_type)} ${lt.escHtml(s.target_name || 'all')} ${lt.escHtml(s.target_detail || '–')} ${lt.escHtml(s.reason)}