diff --git a/static/app.js b/static/app.js index a9c2cd5..60001dc 100644 --- a/static/app.js +++ b/static/app.js @@ -78,7 +78,7 @@ function updateStatusBar(summary, lastCheck) { staleBanner = document.createElement('div'); staleBanner.id = 'stale-banner'; staleBanner.className = 'stale-banner'; - document.querySelector('.main').prepend(staleBanner); + 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.`; @@ -144,7 +144,7 @@ function updateUnifiTable(devices) { const dotClass = d.connected ? 'dot-up' : 'dot-down'; const statusText = d.connected ? 'Online' : 'Offline'; const suppressBtn = !d.connected - ? `` @@ -188,7 +188,7 @@ function updateEventsTable(events, totalActive) { : 'โ€“'; return ` - ${e.severity} + ${e.severity} ${escHtml(e.event_type.replace(/_/g,' '))} ${escHtml(e.target_name)} ${escHtml(e.target_detail || 'โ€“')} @@ -198,7 +198,7 @@ function updateEventsTable(events, totalActive) { ${e.consecutive_failures} ${ticket} - @@ -208,8 +208,9 @@ function updateEventsTable(events, totalActive) { wrap.innerHTML = ` ${countNotice} -
- +
+
+ diff --git a/static/style.css b/static/style.css index 87f582a..40d7abc 100644 --- a/static/style.css +++ b/static/style.css @@ -1,246 +1,127 @@ /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - GANDALF โ€“ Terminal aesthetic (Pulse / TinkerTickets style) + GANDALF โ€“ App-specific styles (extends LotusGuild base.css) + base.css handles: body, header, nav, tables, buttons, forms, modals, + badges, toasts, layout shell, and the full design token system. + This file adds only gandalf-specific components. โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ -/* โ”€โ”€ Variables โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +/* โ”€โ”€ Variable aliases bridging to base.css palette โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ :root { - --bg: #0a0a0a; - --bg2: #1a1a1a; - --bg3: #2a2a2a; - --bg-hover: rgba(0,255,65,.07); + /* Short names used throughout custom components */ + --green: var(--accent-green); + --amber: var(--accent-amber); + --cyan: var(--accent-cyan); + --red: var(--accent-red); + --orange: var(--accent-orange); - --green: #00ff41; - --green-dim: rgba(0,255,65,.15); - --green-dark: #00cc33; - --green-muted: #008822; + --bg: var(--bg-primary); + --bg2: var(--bg-secondary); + --bg3: var(--bg-tertiary); - --amber: #ffb000; - --amber-dim: rgba(255,176,0,.15); + --text: var(--text-primary); + --text-dim: var(--text-secondary); + --border: var(--border-color); + --font: var(--font-mono); + --bg-hover: rgba(255,107,0,.06); - --cyan: #00ffff; - --cyan-dim: rgba(0,255,255,.12); + /* Dim / alpha variants */ + --green-dim: rgba(0,255,136,.10); + --green-muted: rgba(0,255,136,.30); + --amber-dim: rgba(255,179,0,.12); + --cyan-dim: rgba(0,212,255,.12); + --red-dim: rgba(255,45,85,.12); + --orange-dim: rgba(255,107,0,.12); - --red: #ff4444; - --red-dim: rgba(255,68,68,.15); - - --orange: #ff8c00; - --orange-dim: rgba(255,140,0,.15); - - --border: rgba(0,255,65,.35); - --border-hi: #00ff41; - - --text: #00ff41; - --text-dim: #00cc33; - --text-muted: #00bb33; - - --font: 'Courier New','Consolas','Monaco','Menlo',monospace; - - --glow: 0 0 5px #00ff41, 0 0 10px #00ff41, 0 0 15px #00ff41; - --glow-xl: 0 0 8px #00ff41, 0 0 16px #00ff41, 0 0 24px #00ff41, 0 0 32px rgba(0,255,65,.5); - --glow-amber: 0 0 5px #ffb000, 0 0 10px #ffb000, 0 0 15px #ffb000; - --glow-red: 0 0 5px #ff4444, 0 0 10px rgba(255,68,68,.4); - --glow-cyan: 0 0 5px #00ffff, 0 0 10px rgba(0,255,255,.35); - - /* Unified naming aliases โ€” matches base.css variable names */ - --bg-primary: var(--bg); - --bg-secondary: var(--bg2); - --bg-tertiary: var(--bg3); - --terminal-green: var(--green); - --terminal-green-dim: var(--green-dim); - --terminal-amber: var(--amber); - --terminal-amber-dim: var(--amber-dim); - --terminal-cyan: var(--cyan); - --terminal-red: var(--red); - --text-primary: var(--text); - --text-secondary: var(--text-dim); - --border-color: var(--border); - --glow-green: var(--glow); - --font-mono: var(--font); + /* Glow shadows */ + --glow: 0 0 5px var(--accent-green), 0 0 10px rgba(0,255,136,.4); + --glow-amber: 0 0 5px var(--accent-amber), 0 0 10px rgba(255,179,0,.35); + --glow-red: 0 0 5px var(--accent-red), 0 0 10px rgba(255,45,85,.3); + --glow-cyan: 0 0 5px var(--accent-cyan), 0 0 10px rgba(0,212,255,.3); + --glow-xl: 0 0 8px var(--accent-green), 0 0 20px rgba(0,255,136,.5); } -/* โ”€โ”€ Reset โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } - -html { scrollbar-color: var(--green-muted) var(--bg2); scrollbar-width: thin; } -::-webkit-scrollbar { width: 6px; height: 6px; } -::-webkit-scrollbar-track { background: var(--bg2); } -::-webkit-scrollbar-thumb { background: var(--green-muted); } -::-webkit-scrollbar-thumb:hover { background: var(--green); } - -/* โ”€โ”€ Body / CRT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -body { - font-family: var(--font); - background: var(--bg); - color: var(--text); - font-size: 14px; - line-height: 1.5; - min-height: 100vh; - position: relative; - animation: flicker .25s ease-in-out 30s infinite; -} - -/* CRT scanline overlay */ -body::before { - content: ''; - position: fixed; - inset: 0; - background: repeating-linear-gradient( - 0deg, - rgba(0,0,0,0.15) 0px, rgba(0,0,0,0.15) 1px, - transparent 1px, transparent 2px - ); - pointer-events: none; - z-index: 9999; - animation: scanline 8s linear infinite; -} - -/* Binary data stream corner */ -body::after { - content: '10101010'; - position: fixed; - bottom: 10px; right: 14px; - font-family: var(--font); - font-size: .55rem; - color: var(--green); - opacity: .07; - pointer-events: none; - letter-spacing: 2px; - animation: data-stream 3s steps(1) infinite; -} - -@keyframes scanline { to { transform: translateY(4px); } } -@keyframes flicker { 0%,100%{opacity:1} 10%{opacity:.96} 50%{opacity:.98} } -@keyframes data-stream { - 0% { content:'10101010'; } 25% { content:'01010101'; } - 50% { content:'11001100'; } 75% { content:'00110011'; } +/* โ”€โ”€ Animations used by custom components โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +@keyframes pulse-red { + 0%,100% { box-shadow: 0 0 0 0 rgba(255,45,85,.5); } + 50% { box-shadow: 0 0 6px 3px rgba(255,45,85,.2); } } @keyframes pulse-glow { 0%,100% { text-shadow: var(--glow); } 50% { text-shadow: var(--glow-xl); } } -@keyframes pulse-red { - 0%,100% { box-shadow: 0 0 0 0 rgba(255,68,68,.5); } - 50% { box-shadow: 0 0 6px 3px rgba(255,68,68,.2); } -} @keyframes blink { 0%,49%{opacity:1} 50%,100%{opacity:0} } -@keyframes slide-in { - from { transform: translateX(110%); opacity: 0; } - to { transform: translateX(0); opacity: 1; } +@keyframes diag-pulse { + 0%, 100% { box-shadow: none; } + 50% { box-shadow: 0 0 6px rgba(0,212,255,.4); } } -a { color: var(--amber); text-decoration: none; } -a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } - -/* โ”€โ”€ Header โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.header { - background: var(--bg2); - border-bottom: 2px solid var(--green); - box-shadow: 0 2px 16px rgba(0,255,65,.12); - padding: 0 28px; - height: 58px; - display: flex; - align-items: center; - justify-content: space-between; - position: relative; - z-index: 100; -} -.header::before { content:'โ•”'; position:absolute; top:-1px; left:-1px; font-size:1.4rem; color:var(--green); text-shadow:var(--glow); line-height:1; } -.header::after { content:'โ•—'; position:absolute; top:-1px; right:-1px; font-size:1.4rem; color:var(--green); text-shadow:var(--glow); line-height:1; } - -.header-left { display:flex; align-items:center; gap:24px; } -.header-brand { display:flex; flex-direction:column; } - -.header-title { - font-size: 1.35em; - font-weight: bold; - color: var(--amber); - text-shadow: var(--glow-amber); - letter-spacing: .08em; -} -.header-title::before { content:'>> '; color:var(--green); text-shadow:var(--glow); } -.header-sub { font-size: .65em; color: var(--text-muted); letter-spacing: .12em; text-transform: uppercase; } - -.header-nav { display:flex; gap:3px; } - -.nav-link { - color: var(--text-muted); - padding: 5px 12px; - border: 1px solid transparent; - font-size: .8em; - letter-spacing: .06em; - text-transform: uppercase; - transition: all .15s; -} -.nav-link::before { content:'[ '; } -.nav-link::after { content:' ]'; } -.nav-link:hover { - color: var(--green); - border-color: var(--border); - background: var(--green-dim); - text-shadow: var(--glow); - text-decoration: none; -} -.nav-link.active { - color: var(--amber); - border-color: var(--amber); - background: var(--amber-dim); - text-shadow: var(--glow-amber); - text-decoration: none; -} - -.header-right { display:flex; align-items:center; gap:10px; } -.header-user { font-size: .78em; color: var(--text-muted); } -.header-user::before { content:'[USER: '; } -.header-user::after { content:']'; } - -/* โ”€โ”€ Main โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.main { max-width: 1500px; margin: 0 auto; padding: 22px 20px; } - -/* โ”€โ”€ Section โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.section { margin-bottom: 26px; } - -.section-header { +/* โ”€โ”€ Section / page headers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.g-section { margin-bottom: 26px; } +.g-section-header { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; - border-bottom: 1px solid var(--border); - padding-bottom: 5px; + border-bottom: 1px solid var(--border-color); + padding-bottom: 6px; } - -.section-title { - font-size: .9em; +.g-section-title { + font-size: .88em; font-weight: bold; - color: var(--amber); - text-shadow: var(--glow-amber); + color: var(--text-accent); text-transform: uppercase; - letter-spacing: .1em; + letter-spacing: .08em; } -.section-title::before { content:'โ• โ•โ•โ• '; color:var(--green); text-shadow:var(--glow); } -.section-title::after { content:' โ•โ•โ•โ•ฃ'; color:var(--green); text-shadow:var(--glow); } - -.section-badge { +.g-section-badge { font-size: .72em; font-weight: bold; - color: var(--red); - border: 1px solid var(--red); - padding: 0 5px; - text-shadow: var(--glow-red); + color: var(--accent-red); + border: 1px solid var(--accent-red); + padding: 1px 7px; } -.section-badge::before { content:'['; } -.section-badge::after { content:']'; } - -/* โ”€โ”€ Page header โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.page-header { margin-bottom: 18px; } -.page-title { - font-size: 1.05em; +.g-section-badge-resolved { + font-size: .68em; + color: var(--text-muted); + border: 1px solid var(--border-color); + padding: 1px 7px; +} +.g-page-header { margin-bottom: 20px; } +.g-page-title { + font-size: 1em; font-weight: bold; - color: var(--amber); - text-shadow: var(--glow-amber); + color: var(--text-accent); letter-spacing: .06em; } -.page-title::before { content:'>> '; color:var(--green); text-shadow:var(--glow); } -.page-sub { font-size: .75em; color: var(--text-muted); margin-top: 3px; } +.g-page-sub { font-size: .78em; color: var(--text-muted); margin-top: 4px; } + +/* โ”€โ”€ Badge severity color variants (used with lt-badge) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.badge-critical { color: var(--red); border-color: var(--red); text-shadow: var(--glow-red); } +.badge-warning { color: var(--orange); border-color: var(--orange); } +.badge-high { color: var(--orange); border-color: var(--orange); } +.badge-info { color: var(--cyan); border-color: var(--cyan); } +.badge-ok { color: var(--green); border-color: var(--green-muted); } +.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); } + +/* โ”€โ”€ Table row state colors โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.lt-table tr.row-critical td { background: rgba(255,45,85,.04); } +.lt-table tr.row-critical td:first-child { border-left: 2px solid var(--red); } +.lt-table tr.row-warning td { background: rgba(255,107,0,.04); } +.lt-table tr.row-warning td:first-child { border-left: 2px solid var(--orange); } +.lt-table tr.row-resolved td { opacity: .65; } + +/* โ”€โ”€ Table size modifier โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.lt-table-sm th, +.lt-table-sm td { padding: 4px 8px; font-size: .78em; } + +/* โ”€โ”€ Misc table helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.ts-cell { color: var(--text-muted); font-size: .75em; white-space: nowrap; } +.desc-cell { max-width: 280px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.ticket-link{ color: var(--amber); text-shadow: var(--glow-amber); font-weight: bold; } +.empty-state { padding: 28px; text-align: center; color: var(--text-muted); font-size: .82em; } +.pagination-notice { font-size: .8em; color: var(--text-muted); padding: 6px 0 8px; } +.pagination-notice a { color: var(--amber); } /* โ”€โ”€ Status bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ .status-bar { @@ -248,268 +129,45 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } align-items: center; justify-content: space-between; background: var(--bg2); - border: 1px solid var(--border); + border: 1px solid var(--border-color); padding: 9px 16px; margin-bottom: 18px; gap: 12px; flex-wrap: wrap; - position: relative; } -.status-bar::before { content:'โ”Œ'; position:absolute; top:-1px; left:-1px; color:var(--green); font-size:.85rem; line-height:1; } -.status-bar::after { content:'โ”'; position:absolute; top:-1px; right:-1px; color:var(--green); font-size:.85rem; line-height:1; } - -.status-chips { display:flex; gap:7px; flex-wrap:wrap; align-items:center; } - +.status-chips { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; } .chip { font-size: .78em; font-weight: bold; - padding: 2px 9px; + padding: 3px 10px; border: 1px solid; letter-spacing: .04em; } -.chip::before { content:'['; } -.chip::after { content:']'; } -.chip-critical { color:var(--red); border-color:var(--red); text-shadow:var(--glow-red); animation:pulse-glow 2s infinite; } -.chip-warning { color:var(--orange); border-color:var(--orange); } -.chip-ok { color:var(--green); border-color:var(--border); text-shadow:var(--glow); } - -.status-meta { display:flex; align-items:center; gap:10px; white-space:nowrap; } +.chip-critical { color: var(--red); border-color: var(--red); text-shadow: var(--glow-red); animation: pulse-glow 2s infinite; } +.chip-warning { color: var(--orange); border-color: var(--orange); } +.chip-ok { color: var(--green); border-color: var(--green-muted); text-shadow: var(--glow); } +.status-meta { display: flex; align-items: center; gap: 10px; white-space: nowrap; } .last-check { font-size: .72em; color: var(--text-muted); } -.btn-refresh { - background: transparent; - border: 1px solid var(--border); - color: var(--text-muted); - padding: 2px 10px; - font-family: var(--font); - font-size: .75em; - cursor: pointer; - transition: all .15s; -} -.btn-refresh:hover { color:var(--green); border-color:var(--green); background:var(--green-dim); text-shadow:var(--glow); } - -/* โ”€โ”€ Topology โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.topology { - background: var(--bg2); - border: 1px solid var(--border); - padding: 20px 16px 16px; - margin-bottom: 16px; - text-align: center; - overflow-x: auto; - position: relative; -} -.topology::before { content:'โ”Œ'; position:absolute; top:-1px; left:-1px; color:var(--green); font-size:.85rem; line-height:1; } -.topology::after { content:'โ”'; position:absolute; top:-1px; right:-1px; color:var(--green); font-size:.85rem; line-height:1; } - -.topo-row { display:flex; justify-content:center; gap:16px; flex-wrap:wrap; align-items:center; } -.topo-row-internet { margin-bottom:2px; } -.topo-hosts-row { flex-wrap:wrap; gap:10px; } - -.topo-connectors { display:flex; justify-content:center; gap:80px; height:22px; margin:0; } -.topo-connectors.single { gap:0; } -.topo-connectors.wide { gap:44px; } - -.topo-line { width:1px; height:100%; background:var(--green); opacity:.4; } -.topo-line-labeled { position:relative; } -.topo-line-labeled::after { - content: attr(data-link-label); - position: absolute; - left: 6px; top: 50%; - transform: translateY(-50%); - font-size: .62em; +/* โ”€โ”€ Stale monitoring banner โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.stale-banner { + background: var(--amber-dim); + border: 1px solid var(--amber); + border-left: 4px solid var(--amber); color: var(--amber); - text-shadow: var(--glow-amber); - white-space: nowrap; - letter-spacing: .05em; -} - -.topo-node { - display: flex; - flex-direction: column; - align-items: center; - gap: 3px; - padding: 7px 12px; - border: 1px solid var(--border); - background: var(--bg3); - min-width: 94px; - font-size: .75em; - position: relative; - transition: border-color .2s; -} -.topo-node::before { content:'โ”Œ'; position:absolute; top:-1px; left:-1px; color:var(--green); font-size:.8rem; line-height:1; } -.topo-node::after { content:'โ”'; position:absolute; top:-1px; right:-1px; color:var(--green); font-size:.8rem; line-height:1; } - -.topo-internet { border-color:var(--cyan); color:var(--cyan); text-shadow:var(--glow-cyan); font-weight:bold; } -.topo-switch { border-color:var(--amber); color:var(--amber); text-shadow:var(--glow-amber); } -.topo-host { cursor:default; } -.topo-icon { font-size:1.1em; } -.topo-label { font-weight:bold; letter-spacing:.03em; } - -.topo-badge { font-size:.68em; padding:1px 5px; border:1px solid; letter-spacing:.03em; } -.topo-badge-up { color:var(--green); border-color:var(--green); text-shadow:var(--glow); } -.topo-badge-down { color:var(--red); border-color:var(--red); text-shadow:var(--glow-red); animation:pulse-glow 1.5s infinite; } -.topo-badge-degraded { color:var(--orange); border-color:var(--orange); } - -.topo-status-up { border-color:var(--green); box-shadow:0 0 8px rgba(0,255,65,.2); } -.topo-status-down { border-color:var(--red); box-shadow:0 0 8px rgba(255,68,68,.3); } -.topo-status-degraded { border-color:var(--orange); box-shadow:0 0 8px rgba(255,140,0,.2); } - -.topo-status-dot { width:7px; height:7px; border:1px solid var(--text-muted); background:transparent; position:absolute; top:5px; right:5px; } - -/* Topology subtitle text */ -.topo-node-sub { - font-size: .58em; - color: var(--text-muted); - letter-spacing: .02em; - font-weight: normal; -} - -.topo-badge-unknown { color:var(--text-muted); border-color:var(--border); } - -.topo-vlan-tag { - color: var(--cyan) !important; - opacity: .7; - font-size: .54em !important; -} - -/* Switch tier: two switches with horizontal connector */ -.topo-switch-tier { - display: flex; - align-items: center; - gap: 0; -} - -.topo-h-link { - display: flex; - flex-direction: column; - align-items: center; - padding: 0 8px; -} - -.topo-h-link-line { - width: 70px; - height: 1px; - background: var(--amber); - opacity: .5; -} - -.topo-h-link-label { - font-size: .52em; - color: var(--amber); - opacity: .7; - margin-top: 3px; - letter-spacing: .04em; -} - -/* Host tier: two groups side by side */ -.topo-host-tier { - display: flex; - justify-content: center; - align-items: flex-start; - gap: 40px; - margin-top: 0; -} - -.topo-host-group { flex-shrink: 0; } - -/* PoE host group: offset right to sit below PoE switch */ -.topo-poe-hosts { - padding-top: 0; -} - -/* Off-rack node (dashed border) */ -.topo-host-table { - border-style: dashed; -} - -/* Dashed 10G line (for off-rack/table host) */ -.topo-line-dashed { - background: none; - border-left: 1px dashed var(--green); - opacity: .4; -} - -/* 1G management band โ€” horizontal amber dashed line with label */ -.topo-mgmt-band { - display: flex; - align-items: center; - gap: 6px; - padding: 0 8px; - height: 16px; -} - -.topo-mgmt-label { - font-size: .52em; - color: var(--amber); - opacity: .65; - white-space: nowrap; - letter-spacing: .04em; -} - -.topo-mgmt-line { - flex: 1; - height: 1px; - border-top: 1px dashed var(--amber); - opacity: .4; -} - -/* โ”€โ”€ Host cards โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.host-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(248px, 1fr)); - gap: 12px; -} - -.host-card { - background: var(--bg2); - border: 1px solid var(--border); - padding: 12px; - position: relative; - transition: border-color .2s, box-shadow .2s; -} -.host-card::before { content:'โ”Œ'; position:absolute; top:-1px; left:-1px; color:var(--green); font-size:.85rem; line-height:1; } -.host-card::after { content:'โ”'; position:absolute; top:-1px; right:-1px; color:var(--green); font-size:.85rem; line-height:1; } -.host-card:hover { border-color:var(--green); box-shadow:0 0 12px rgba(0,255,65,.12); } - -.host-card-up { border-left: 3px solid var(--green); } -.host-card-down { border-left: 3px solid var(--red); box-shadow: inset 3px 0 10px rgba(255,68,68,.08); } -.host-card-degraded { border-left: 3px solid var(--orange); } - -.host-card-header { margin-bottom: 8px; } -.host-name-row { display:flex; align-items:center; gap:6px; margin-bottom:3px; } - -.host-name { - font-weight: bold; + padding: 10px 16px; + margin: 0 0 14px; font-size: .88em; - color: var(--amber); - text-shadow: var(--glow-amber); - letter-spacing: .04em; } -.host-meta { display:flex; gap:6px; align-items:center; flex-wrap:wrap; } -.host-ip { font-size:.72em; color:var(--text-muted); letter-spacing:.02em; } - -.host-source { - font-size: .65em; - padding: 1px 5px; - border: 1px solid; - letter-spacing: .04em; - font-weight: bold; +/* โ”€โ”€ Error / empty state containers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.error-state { + padding: 16px 20px; + border-left: 3px solid var(--red); + background: var(--red-dim); + color: var(--red); + font-size: .88em; } -.source-prometheus { color:#e8703a; border-color:rgba(232,112,58,.4); } -.source-ping { color:var(--cyan); border-color:var(--cyan-dim); } - -.iface-list { border-top:1px solid var(--border); padding-top:6px; margin-bottom:8px; } -.iface-row { display:flex; align-items:center; gap:6px; padding:2px 0; } -.iface-name { font-size:.78em; flex:1; color:var(--text-dim); letter-spacing:.01em; } -.iface-state { font-size:.72em; font-weight:bold; letter-spacing:.04em; } - -.state-up { color:var(--green); text-shadow:var(--glow); } -.state-down { color:var(--red); text-shadow:var(--glow-red); } -.state-initial_down { color:var(--text-muted); } - -.host-ping-note { font-size:.72em; color:var(--text-muted); border-top:1px solid var(--border); padding-top:6px; margin-bottom:8px; } -.host-actions { border-top:1px solid var(--border); padding-top:7px; display:flex; gap:5px; flex-wrap:wrap; } /* โ”€โ”€ Status dots โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ .host-status-dot, .iface-dot, @@ -518,236 +176,67 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } width: 8px; height: 8px; border: 1px solid; flex-shrink: 0; -} -.dot-up, .host-status-dot.dot-up { border-color:var(--green); background:var(--green); box-shadow:0 0 4px var(--green); } -.dot-down, .host-status-dot.dot-down { border-color:var(--red); background:var(--red); animation:pulse-red 1.5s infinite; } -.dot-degraded { border-color:var(--orange); background:var(--orange); } -.dot-unknown, .dot-initial_down { border-color:var(--text-muted); background:transparent; } - -/* โ”€โ”€ Badges โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.badge { - display: inline-block; - font-size: .7em; - font-weight: bold; - padding: 1px 6px; - border: 1px solid; - letter-spacing: .05em; - text-transform: uppercase; -} -.badge::before { content:'['; } -.badge::after { content:']'; } -.badge-critical { color:var(--red); border-color:var(--red); text-shadow:var(--glow-red); } -.badge-warning { color:var(--orange); border-color:var(--orange); } -.badge-info { color:var(--cyan); border-color:var(--cyan-dim); } -.badge-ok { color:var(--green); border-color:var(--border); text-shadow:var(--glow); } -.badge-neutral { color:var(--text-muted); border-color:var(--text-muted); } -.badge-suppressed{ font-size:.9em; padding:0; border:none; color:var(--text-muted); } - -/* โ”€โ”€ Tables โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.table-wrap { - background: var(--bg2); - border: 1px solid var(--border); - overflow: hidden; - position: relative; -} -.table-wrap::before { content:'โ”Œ'; position:absolute; top:-1px; left:-1px; color:var(--green); font-size:.85rem; line-height:1; } -.table-wrap::after { content:'โ”'; position:absolute; top:-1px; right:-1px; color:var(--green); font-size:.85rem; line-height:1; } - -.data-table { width:100%; border-collapse:collapse; } - -.data-table th { - background: var(--bg3); - padding: 8px 12px; - text-align: left; - font-size: .7em; - font-weight: bold; - color: var(--amber); - text-transform: uppercase; - letter-spacing: .08em; - border-bottom: 1px solid var(--border); - white-space: nowrap; - text-shadow: var(--glow-amber); -} -.data-table th::before { content:'> '; color:var(--green); } - -.data-table td { - padding: 7px 12px; - border-bottom: 1px solid rgba(0,255,65,.08); vertical-align: middle; - font-size: .83em; } -.data-table tr:last-child td { border-bottom:none; } -.data-table tr:hover td { background:var(--bg-hover); } +.dot-up, .host-status-dot.dot-up { border-color: var(--green); background: var(--green); box-shadow: 0 0 4px var(--green); } +.dot-down, .host-status-dot.dot-down { border-color: var(--red); background: var(--red); animation: pulse-red 1.5s infinite; } +.dot-degraded { border-color: var(--orange); background: var(--orange); } +.dot-unknown, .dot-initial_down { border-color: var(--text-muted); background: transparent; } -.row-critical td { background:rgba(255,68,68,.03); } -.row-critical td:first-child { border-left:2px solid var(--red); } -.row-warning td { background:rgba(255,140,0,.03); } -.row-warning td:first-child { border-left:2px solid var(--orange); } -.row-resolved td { opacity:.5; } - -.data-table-sm td, .data-table-sm th { padding:5px 10px; } - -.ts-cell { color:var(--text-muted); font-size:.75em; white-space:nowrap; } -.desc-cell { max-width:280px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } -.ticket-link{ color:var(--amber); text-shadow:var(--glow-amber); font-weight:bold; } - -.empty-state { padding:28px; text-align:center; color:var(--text-muted); font-size:.82em; } -.empty-row td{ text-align:center; color:var(--text-muted); } - -/* โ”€โ”€ Buttons โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.btn { - display: inline-flex; - align-items: center; - gap: 5px; - padding: 5px 12px; - border: 2px solid; - cursor: pointer; - font-family: var(--font); - font-size: .8em; - font-weight: bold; - letter-spacing: .05em; - text-transform: uppercase; - background: transparent; - transition: all .15s; +/* โ”€โ”€ Host cards โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.host-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(248px, 1fr)); + gap: 12px; + margin-top: 14px; } -.btn::before { content: '[ '; } -.btn::after { content: ' ]'; } -.btn:hover { transform: translateY(-2px); } - -.btn-primary { color:var(--green); border-color:var(--green); text-shadow:var(--glow); } -.btn-primary::before { content:'> '; color:var(--amber); } -.btn-primary::after { content:''; } -.btn-primary:hover { background:var(--green-dim); box-shadow:var(--glow); } - -.btn-secondary { color:var(--text-dim); border-color:var(--border); } -.btn-secondary:hover { color:var(--green); border-color:var(--green); background:var(--bg-hover); } - -.btn-danger { color:var(--red); border-color:rgba(255,68,68,.35); } -.btn-danger:hover { background:var(--red-dim); border-color:var(--red); text-shadow:var(--glow-red); } - -.btn-lg { padding:8px 18px; font-size:.85em; } - -.btn-sm { - padding: 2px 8px; - font-family: var(--font); - font-size: .7em; - font-weight: bold; - border: 2px solid; - cursor: pointer; - background: transparent; +.host-card { + background: var(--bg2); + border: 1px solid var(--border-color); + padding: 12px; + position: relative; + transition: border-color .2s, box-shadow .2s; +} +.host-card:hover { border-color: var(--accent-orange); box-shadow: 0 0 12px rgba(255,107,0,.1); } +.host-card-up { border-left: 3px solid var(--green); } +.host-card-down { border-left: 3px solid var(--red); box-shadow: inset 3px 0 10px rgba(255,45,85,.08); } +.host-card-degraded { border-left: 3px solid var(--orange); } +.host-card-header { margin-bottom: 8px; } +.host-name-row { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; } +.host-name { font-weight: bold; font-size: .88em; color: var(--amber); letter-spacing: .04em; } +.host-meta { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; } +.host-ip { font-size: .72em; color: var(--text-muted); } +.host-source { + font-size: .65em; + padding: 1px 5px; + border: 1px solid; letter-spacing: .04em; - transition: all .15s; -} -.btn-suppress { color:var(--text-muted); border-color:var(--text-muted); } -.btn-suppress:hover { color:var(--amber); border-color:var(--amber); } -.btn-danger.btn-sm { color:var(--red); border-color:rgba(255,68,68,.35); } -.btn-danger.btn-sm:hover{ color:var(--red); border-color:var(--red); text-shadow:var(--glow-red); } - -/* โ”€โ”€ Modal โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.modal-overlay { - position: fixed; - inset: 0; - background: rgba(0,0,0,.8); - z-index: 200; - display: flex; - align-items: center; - justify-content: center; -} -.modal { - background: var(--bg2); - border: 3px double var(--green); - box-shadow: 0 0 30px rgba(0,255,65,.2), 0 8px 40px rgba(0,0,0,.8); - width: 480px; - max-width: 95vw; - padding: 20px; - position: relative; -} -.modal::before { content:'โ•”'; position:absolute; top:-1px; left:-1px; color:var(--green); text-shadow:var(--glow); font-size:.9rem; line-height:1; } -.modal::after { content:'โ•—'; position:absolute; top:-1px; right:-1px; color:var(--green); text-shadow:var(--glow); font-size:.9rem; line-height:1; } - -.modal-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 16px; - border-bottom: 1px solid var(--border); - padding-bottom: 10px; -} -.modal-header h3 { font-size:.88em; color:var(--amber); text-shadow:var(--glow-amber); text-transform:uppercase; letter-spacing:.08em; } -.modal-header h3::before { content:'>> '; color:var(--green); } -.modal-close { - background: none; - border: 1px solid var(--border); - cursor: pointer; - font-size: .82em; - color: var(--text-muted); - padding: 2px 8px; - font-family: var(--font); - transition: all .15s; -} -.modal-close:hover { color:var(--red); border-color:var(--red); } - -.modal-actions { - display: flex; - gap: 8px; - justify-content: flex-end; - margin-top: 16px; - padding-top: 12px; - border-top: 1px solid var(--border); -} - -/* โ”€โ”€ Forms โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.form-card { - background: var(--bg2); - border: 1px solid var(--border); - padding: 16px; - position: relative; -} -.form-card::before { content:'โ”Œ'; position:absolute; top:-1px; left:-1px; color:var(--green); font-size:.85rem; line-height:1; } -.form-card::after { content:'โ”'; position:absolute; top:-1px; right:-1px; color:var(--green); font-size:.85rem; line-height:1; } - -.form-row { display:flex; gap:12px; flex-wrap:wrap; margin-bottom:10px; } -.form-row-align { align-items:flex-end; } -.form-group { display:flex; flex-direction:column; gap:4px; min-width:150px; flex:1; } -.form-group-wide{ flex:3; } -.form-group-submit { flex:0 0 auto; min-width:unset; } - -.form-group label { - font-size: .7em; font-weight: bold; - color: var(--amber); - text-transform: uppercase; - letter-spacing: .07em; - text-shadow: var(--glow-amber); } +.source-prometheus { color: var(--orange); border-color: var(--orange-dim); } +.source-ping { color: var(--cyan); border-color: var(--cyan-dim); } +.iface-list { border-top: 1px solid var(--border-color); padding-top: 6px; margin-bottom: 8px; } +.iface-row { display: flex; align-items: center; gap: 6px; padding: 2px 0; } +.iface-name { font-size: .78em; flex: 1; color: var(--text-dim); } +.iface-state { font-size: .72em; font-weight: bold; letter-spacing: .04em; } +.state-up { color: var(--green); text-shadow: var(--glow); } +.state-down { color: var(--red); text-shadow: var(--glow-red); } +.state-initial_down { color: var(--text-muted); } +.host-ping-note { font-size: .72em; color: var(--text-muted); border-top: 1px solid var(--border-color); padding-top: 6px; margin-bottom: 8px; } +.host-actions { border-top: 1px solid var(--border-color); padding-top: 7px; display: flex; gap: 5px; flex-wrap: wrap; } -.form-group input, -.form-group select { - padding: 6px 9px; - border: 2px solid var(--border); - font-family: var(--font); - font-size: .8em; - background: var(--bg3); - color: var(--text); - transition: border-color .15s, box-shadow .15s; -} -.form-group input::placeholder { color: var(--text-muted); } -.form-group input:focus, -.form-group select:focus { - outline: none; - border-color: var(--amber); - box-shadow: 0 0 6px rgba(255,176,0,.18); -} -.form-group select option { background: var(--bg3); color: var(--text); } - -.form-hint { font-size:.7em; color:var(--text-muted); margin-top:2px; } -.required { color:var(--red); } +/* โ”€โ”€ Form layout helpers (used in suppressions.html form grids) โ”€โ”€โ”€โ”€โ”€ */ +.form-row { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; } +.form-row-align { align-items: flex-end; } +.form-group-wide { flex: 3; } +.form-group-submit { flex: 0 0 auto; min-width: unset; } +.required { color: var(--red); } /* โ”€โ”€ Duration pills โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.duration-pills { display:flex; gap:5px; flex-wrap:wrap; margin-bottom:5px; } +.duration-pills { display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: 6px; } .pill { padding: 3px 10px; - border: 1px solid var(--border); + border: 1px solid var(--border-color); background: transparent; font-family: var(--font); font-size: .72em; @@ -757,786 +246,33 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } transition: all .15s; letter-spacing: .04em; } -.pill:hover { border-color:var(--green); color:var(--green); background:var(--green-dim); } +.pill:hover { border-color: var(--green); color: var(--green); background: var(--green-dim); } .pill.active, -.pill-manual.active { border-color:var(--amber); color:var(--amber); background:var(--amber-dim); text-shadow:var(--glow-amber); } +.pill-manual.active { border-color: var(--amber); color: var(--amber); background: var(--amber-dim); text-shadow: var(--glow-amber); } -/* โ”€โ”€ Targets grid โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.targets-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(180px, 1fr)); gap:10px; } +/* โ”€โ”€ Available targets grid (suppressions page) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.targets-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; } .target-card { background: var(--bg2); - border: 1px solid var(--border); + border: 1px solid var(--border-color); padding: 10px; - position: relative; } -.target-card::before { content:'โ”Œ'; position:absolute; top:-1px; left:-1px; color:var(--green); font-size:.78rem; line-height:1; } -.target-card::after { content:'โ”'; position:absolute; top:-1px; right:-1px; color:var(--green); font-size:.78rem; line-height:1; } +.target-name { font-weight: bold; font-size: .82em; margin-bottom: 3px; color: var(--amber); } +.target-type { font-size: .7em; color: var(--text-muted); margin-bottom: 6px; } +.target-ifaces{ display: flex; flex-wrap: wrap; gap: 3px; } +.iface-chip { font-family: var(--font); font-size: .65em; background: var(--bg3); border: 1px solid var(--border-color); padding: 1px 5px; color: var(--text-dim); } -.target-name { font-weight:bold; font-size:.82em; margin-bottom:3px; color:var(--amber); } -.target-type { font-size:.7em; color:var(--text-muted); margin-bottom:6px; } -.target-ifaces{ display:flex; flex-wrap:wrap; gap:3px; } -.iface-chip { font-family:var(--font); font-size:.65em; background:var(--bg3); border:1px solid var(--border); padding:1px 5px; color:var(--text-dim); } - -/* โ”€โ”€ Toast โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.toast-container { position:fixed; bottom:20px; right:20px; z-index:300; display:flex; flex-direction:column; gap:7px; } -.toast { - padding: 9px 16px; - border: 1px solid; - font-family: var(--font); - font-size: .8em; - font-weight: bold; +/* โ”€โ”€ Topology diagram โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.topology { background: var(--bg2); - animation: slide-in .15s ease; - letter-spacing: .04em; + border: 1px solid var(--border-color); + padding: 20px 16px 16px; + margin-bottom: 16px; + text-align: center; + overflow-x: auto; } -.toast::before { content:'>> '; } -.toast-success { color:var(--green); border-color:var(--green); text-shadow:var(--glow); } -.toast-error { color:var(--red); border-color:var(--red); text-shadow:var(--glow-red); } -/* โ”€โ”€ Link debug page โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.link-host-list { display:flex; flex-direction:column; gap:18px; } - -.link-host-panel { - background: var(--bg2); - border: 1px solid var(--border); - position: relative; -} -.link-host-panel::before { content:'โ•”'; position:absolute; top:-1px; left:-1px; color:var(--green); text-shadow:var(--glow); font-size:1rem; line-height:1; } -.link-host-panel::after { content:'โ•—'; position:absolute; top:-1px; right:-1px; color:var(--green); text-shadow:var(--glow); font-size:1rem; line-height:1; } - -.link-host-title { - display: flex; - align-items: center; - gap: 12px; - padding: 9px 16px; - background: var(--bg3); - border-bottom: 1px solid var(--border); -} -.link-host-name { font-weight:bold; font-size:.88em; color:var(--amber); text-shadow:var(--glow-amber); letter-spacing:.05em; } -.link-host-name::before { content:'>> '; color:var(--green); } -.link-host-ip { font-size:.72em; color:var(--text-muted); } -.link-host-upd { font-size:.65em; color:var(--text-muted); margin-left:auto; } - -.link-ifaces-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); -} - -.link-iface-card { - border-right: 1px solid rgba(0,255,65,.15); - border-bottom: 1px solid rgba(0,255,65,.15); - padding: 12px 14px; -} -.link-iface-card:last-child { border-right:none; } - -.link-iface-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 10px; - padding-bottom: 6px; - border-bottom: 1px solid rgba(0,255,65,.12); -} -.link-iface-name { font-weight:bold; font-size:.84em; color:var(--amber); text-shadow:var(--glow-amber); flex:1; } -.link-iface-speed { font-size:.75em; color:var(--cyan); text-shadow:var(--glow-cyan); font-weight:bold; } -.link-iface-type { font-size:.65em; color:var(--text-muted); padding:1px 5px; border:1px solid var(--text-muted); letter-spacing:.04em; } -.link-iface-type.type-fibre { color:var(--cyan); border-color:var(--cyan-dim); text-shadow:var(--glow-cyan); } -.link-iface-type.type-copper{ color:var(--green); border-color:var(--border); } -.link-iface-type.type-da { color:var(--amber); border-color:var(--amber-dim); } - -/* Link stats 2-column grid */ -.link-stats-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 6px 16px; - margin-bottom: 10px; -} -.link-stat { display:flex; flex-direction:column; gap:1px; } -.link-stat-label { font-size:.6em; color:var(--text-muted); text-transform:uppercase; letter-spacing:.07em; } -.link-stat-value { font-size:.78em; font-weight:bold; color:var(--text-dim); } -.val-good { color:var(--green); text-shadow:var(--glow); } -.val-warn { color:var(--orange); } -.val-crit { color:var(--red); text-shadow:var(--glow-red); } -.val-neutral { color:var(--text-muted); } -.val-cyan { color:var(--cyan); text-shadow:var(--glow-cyan); } - -/* Traffic bars */ -.traffic-section { margin-top:8px; padding-top:8px; border-top:1px solid rgba(0,255,65,.1); } -.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); border:1px solid rgba(0,255,65,.2); 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,255,255,.4); } -.traffic-rx { background:var(--green); box-shadow:0 0 3px rgba(0,255,65,.4); } -.traffic-value { font-size:.7em; color:var(--text-dim); width:68px; text-align:right; flex-shrink:0; } - -/* SFP / optical panel */ -.sfp-panel { - margin-top: 10px; - padding: 10px 10px 8px; - background: var(--bg3); - border: 1px solid rgba(0,255,255,.2); - position: relative; -} -.sfp-panel::before { - content: '[ SFP / OPTICAL ]'; - position: absolute; - top: -8px; left: 10px; - font-size: .6em; - color: var(--cyan); - text-shadow: var(--glow-cyan); - background: var(--bg3); - padding: 0 4px; - letter-spacing: .09em; - font-weight: bold; -} - -.sfp-vendor-row { font-size:.7em; color:var(--text-muted); margin-bottom:8px; } -.sfp-vendor-row span { color:var(--text-dim); } - -.sfp-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 6px 12px; -} - -.sfp-stat { display:flex; flex-direction:column; gap:1px; } -.sfp-stat-label { font-size:.58em; color:var(--text-muted); text-transform:uppercase; letter-spacing:.07em; } -.sfp-stat-value { font-size:.78em; font-weight:bold; } - -/* Power level with bar */ -.power-row { display:flex; align-items:center; gap:5px; margin-top:1px; } -.power-track { flex:1; height:3px; background:var(--bg); border:1px solid rgba(0,255,65,.2); position:relative; overflow:hidden; } -.power-fill { height:100%; position:absolute; left:0; top:0; transition:width .4s; } -.power-ok { background:var(--green); box-shadow:0 0 3px var(--green); } -.power-warn { background:var(--orange); } -.power-crit { background:var(--red); box-shadow:0 0 3px var(--red); } - -/* Collapsible link panels */ -.link-host-title { - cursor: pointer; - user-select: none; -} -.link-host-title:hover { background: rgba(0,255,65,.04); } - -.panel-toggle { - font-size: .65em; - color: var(--text-muted); - letter-spacing: .04em; - flex-shrink: 0; - margin-left: 6px; - padding: 0 4px; - border: 1px solid rgba(0,255,65,.2); -} -.link-host-panel.collapsed > .link-ifaces-grid { display: none; } - -/* Collapse all / Expand all bar */ -.link-collapse-bar { - display: flex; - gap: 8px; - margin-bottom: 10px; -} - -/* Link panel states */ -.link-no-data { padding:14px; color:var(--text-muted); font-size:.78em; text-align:center; } -.link-loading { padding:20px; text-align:center; color:var(--text-muted); font-size:.8em; } -.link-loading::after { content:' ...'; animation:blink 1s step-end infinite; } - -/* Counters (errors/drops) */ -.counter-zero { color:var(--green); } -.counter-nonzero { color:var(--red); text-shadow:var(--glow-red); } - -/* UniFi switch 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; - text-shadow: var(--glow-cyan); -} -.unifi-section-header::before, -.unifi-section-header::after { - content: ''; - flex: 1; - height: 1px; - background: linear-gradient(90deg, transparent, var(--cyan), transparent); -} - -/* Port badges (UPLINK, PoE, #N) */ -.port-badge { - font-size: .58em; - padding: 1px 5px; - border: 1px solid; - letter-spacing: .05em; - font-weight: bold; - vertical-align: middle; -} -.port-badge-uplink { color:var(--amber); border-color:var(--amber-dim); } -.port-badge-poe { color:var(--cyan); border-color:var(--cyan-dim); } -.port-badge-num { color:var(--text-muted); border-color:rgba(0,255,65,.2); } - -/* LLDP neighbor + PoE info lines on link debug cards */ -.port-lldp { - font-size: .68em; - color: var(--cyan); - text-shadow: var(--glow-cyan); - margin: -4px 0 6px; - letter-spacing: .02em; -} -.port-poe-info { - font-size: .68em; - color: var(--amber); - margin: -4px 0 6px; - letter-spacing: .02em; -} - -/* Amber value colour used in inspector */ -.val-amber { color:var(--amber); text-shadow:var(--glow-amber); } - -/* Down port card โ€” dim everything */ -.link-iface-card.port-down { - opacity: .42; - filter: saturate(.3); -} - -/* โ”€โ”€ Inspector page โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ - -/* Layout: main chassis area + collapsible right panel */ -.inspector-layout { - display: flex; - gap: 16px; - align-items: flex-start; - min-height: 300px; -} - -.inspector-main { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 14px; -} - -/* Switch chassis card */ -.inspector-chassis { - background: var(--bg2); - border: 1px solid var(--border); - position: relative; -} -.inspector-chassis::before { content:'โ•”'; position:absolute; top:-1px; left:-1px; color:var(--green); text-shadow:var(--glow); font-size:1rem; line-height:1; } -.inspector-chassis::after { content:'โ•—'; position:absolute; top:-1px; right:-1px; color:var(--green); text-shadow:var(--glow); font-size:1rem; line-height:1; } - -.chassis-header { - display: flex; - align-items: center; - gap: 12px; - padding: 8px 16px; - background: var(--bg3); - border-bottom: 1px solid var(--border); -} -.chassis-name { font-weight:bold; font-size:.88em; color:var(--amber); text-shadow:var(--glow-amber); letter-spacing:.05em; } -.chassis-name::before { content:'>> '; color:var(--green); } -.chassis-ip { font-size:.72em; color:var(--text-muted); } -.chassis-meta { font-size:.65em; color:var(--text-muted); margin-left:auto; } - -.chassis-body { - padding: 12px 16px 14px; -} - -/* Port rows */ -.chassis-rows { display:flex; flex-direction:column; gap:5px; margin-bottom:8px; } -.chassis-row { display:flex; flex-wrap:wrap; gap:4px; } - -/* SFP section below main rows */ -.chassis-sfp-section { - display: flex; - gap: 6px; - padding-top: 8px; - border-top: 1px solid rgba(0,255,255,.15); - margin-top: 4px; -} - -/* Individual port block */ -.switch-port-block { - width: 34px; - height: 34px; - display: flex; - align-items: center; - justify-content: center; - font-size: .6em; - font-weight: bold; - border: 1px solid; - cursor: pointer; - transition: box-shadow .1s, border-color .1s, background .1s; - user-select: none; - flex-shrink: 0; - letter-spacing: 0; -} - -/* SFP port (in rows) โ€” width overridden to 34px further down */ -.switch-port-block.sfp-port { - width: 34px; - height: 38px; - font-size: .55em; -} - -/* SFP section block (standalone cage) */ -.switch-port-block.sfp-block { - width: 44px; - height: 30px; - font-size: .55em; - letter-spacing: .04em; -} - -/* State colours */ -.switch-port-block.down { - background: var(--bg3); - border-color: rgba(0,255,65,.15); - color: rgba(0,255,65,.25); -} -.switch-port-block.up { - background: rgba(0,255,65,.06); - border-color: var(--green-muted); - color: var(--green); - text-shadow: 0 0 4px rgba(0,255,65,.5); -} -.switch-port-block.up:hover { - background: rgba(0,255,65,.13); - border-color: var(--green); - box-shadow: var(--glow); -} -.switch-port-block.poe-active { - background: var(--amber-dim); - border-color: var(--amber); - color: var(--amber); - text-shadow: 0 0 4px rgba(255,176,0,.5); -} -.switch-port-block.poe-active:hover { - box-shadow: var(--glow-amber); -} -.switch-port-block.uplink { - background: var(--cyan-dim); - border-color: var(--cyan); - color: var(--cyan); - text-shadow: 0 0 4px rgba(0,255,255,.5); -} -.switch-port-block.uplink:hover { - box-shadow: var(--glow-cyan); -} -.switch-port-block.selected { - outline: 2px solid #fff; - outline-offset: 1px; -} - -/* Right-side detail panel */ -.inspector-panel { - width: 0; - overflow: hidden; - flex-shrink: 0; - transition: width .2s ease; - display: flex; - flex-direction: column; -} -.inspector-panel.open { - width: 310px; -} - -.inspector-panel-inner { - width: 310px; - background: var(--bg2); - border: 1px solid var(--border); - padding: 14px 14px 18px; - position: relative; - overflow-y: auto; - max-height: calc(100vh - 120px); -} -.inspector-panel-inner::before { content:'โ•”'; position:absolute; top:-1px; left:-1px; color:var(--green); text-shadow:var(--glow); font-size:1rem; line-height:1; } -.inspector-panel-inner::after { content:'โ•—'; position:absolute; top:-1px; right:-1px; color:var(--green); text-shadow:var(--glow); font-size:1rem; line-height:1; } - -.panel-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 12px; - padding-bottom: 10px; - border-bottom: 1px solid var(--border); -} -.panel-port-name { font-weight:bold; font-size:.92em; color:var(--amber); text-shadow:var(--glow-amber); } -.panel-meta { font-size:.68em; color:var(--text-muted); margin-top:2px; } -.panel-close { - background: none; - border: 1px solid var(--border); - color: var(--text-muted); - cursor: pointer; - font-size: .8em; - padding: 1px 7px; - font-family: var(--font); - flex-shrink: 0; - transition: all .15s; -} -.panel-close:hover { color:var(--red); border-color:var(--red); } - -.panel-section-title { - font-size: .62em; - font-weight: bold; - color: var(--amber); - text-shadow: var(--glow-amber); - text-transform: uppercase; - letter-spacing: .1em; - margin: 10px 0 5px; - padding-bottom: 3px; - border-bottom: 1px solid rgba(0,255,65,.12); -} -.panel-section-title:first-of-type { margin-top: 0; } - -.panel-row { - display: flex; - justify-content: space-between; - align-items: baseline; - padding: 2px 0; -} -.panel-label { font-size:.68em; color:var(--text-muted); text-transform:uppercase; letter-spacing:.05em; flex-shrink:0; } -.panel-val { font-size:.75em; font-weight:bold; color:var(--text-dim); text-align:right; word-break:break-all; } - -/* Path debug two-column layout */ -.path-conn-type { - font-size: .68em; - color: var(--cyan); - font-weight: normal; - margin-left: 6px; - text-shadow: none; - text-transform: none; - letter-spacing: normal; -} - -.path-debug-cols { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; - margin-top: 6px; -} - -.path-col { - background: var(--bg3); - border: 1px solid rgba(0,255,65,.18); - padding: 7px 8px; -} -.path-col-header { - font-size: .62em; - font-weight: bold; - color: var(--amber); - margin-bottom: 5px; - padding-bottom: 3px; - border-bottom: 1px solid rgba(0,255,65,.15); - letter-spacing: .04em; -} -.path-row { - display: flex; - justify-content: space-between; - gap: 4px; - font-size: .65em; - padding: 1px 0; -} -.path-row span:first-child { color:var(--text-muted); flex-shrink:0; } -.path-row span:last-child { color:var(--text-dim); font-weight:bold; text-align:right; word-break:break-all; } - -.path-dom { - margin-top: 5px; - padding-top: 5px; - border-top: 1px solid rgba(0,255,255,.15); -} -.path-dom-row { - display: flex; - justify-content: space-between; - font-size: .65em; - padding: 1px 0; - color: var(--cyan); -} -.path-dom-row span:first-child { color:var(--text-muted); } - -/* โ”€โ”€ Link Diagnostics โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.diag-bar { - display: flex; - align-items: center; - gap: 10px; - margin-top: 14px; - padding-top: 10px; - border-top: 1px solid var(--border); -} - -.btn-diag { - font-family: var(--font); - font-size: .65em; - color: var(--cyan); - background: transparent; - border: 1px solid var(--cyan); - padding: 4px 10px; - cursor: pointer; - letter-spacing: .04em; - transition: background .15s, box-shadow .15s; - animation: diag-pulse 2.5s ease-in-out infinite; -} -.btn-diag:hover { - background: var(--cyan-dim); - box-shadow: var(--glow-cyan); -} - -@keyframes diag-pulse { - 0%, 100% { box-shadow: none; } - 50% { box-shadow: 0 0 6px rgba(0,255,255,.4); } -} - -.diag-status { - font-size: .6em; - color: var(--text-muted); - font-style: italic; -} - -.diag-error { - color: var(--red); - font-size: .65em; - margin-top: 8px; -} - -.diag-results { - margin-top: 4px; -} - -.diag-results-inner { - display: flex; - flex-direction: column; - gap: 6px; -} - -/* Health banner */ -.diag-health-banner { - display: flex; - gap: 8px; - padding: 6px 0 4px; - margin-bottom: 2px; -} - -.diag-health-critical { - background: var(--red-dim); - color: var(--red); - border: 1px solid var(--red); - padding: 2px 8px; - font-size: .62em; - font-weight: bold; - letter-spacing: .05em; -} - -.diag-health-warning { - background: var(--amber-dim); - color: var(--amber); - border: 1px solid var(--amber); - padding: 2px 8px; - font-size: .62em; - font-weight: bold; - letter-spacing: .05em; -} - -.diag-health-ok { - background: var(--green-dim); - color: var(--green); - border: 1px solid var(--green); - padding: 2px 8px; - font-size: .62em; - font-weight: bold; - letter-spacing: .05em; -} - -/* Issue list */ -.diag-issue-list { - display: flex; - flex-direction: column; - gap: 3px; -} - -.diag-issue-row { - font-size: .62em; - padding: 3px 6px; - background: var(--bg2); - border-left: 2px solid var(--border); - line-height: 1.4; -} - -.diag-code { - font-weight: bold; - color: var(--amber); -} - -/* Sections */ -.diag-section { - background: var(--bg2); - border: 1px solid rgba(0,255,65,.12); -} - -.diag-section-header { - font-size: .62em; - font-weight: bold; - color: var(--amber); - padding: 4px 8px; - letter-spacing: .04em; - border-bottom: 1px solid rgba(0,255,65,.12); - background: rgba(255,176,0,.04); -} - -/* Collapsible sections */ -.diag-collapsible .diag-section-body { - display: none; -} - -.diag-collapsible.diag-open .diag-section-body { - display: block; -} - -.diag-toggle { - cursor: pointer; - user-select: none; -} - -.diag-toggle-hint { - font-weight: normal; - color: var(--text-muted); - font-size: .9em; -} - -.diag-collapsible.diag-open .diag-toggle-hint::after { - content: ''; -} - -/* Data tables */ -.diag-table { - width: 100%; - border-collapse: collapse; - font-size: .62em; -} - -.diag-table td { - padding: 3px 8px; - vertical-align: top; -} - -.diag-table td:first-child { - color: var(--text-muted); - width: 40%; - white-space: nowrap; -} - -.diag-table td:last-child { - color: var(--text-dim); - font-weight: bold; - word-break: break-all; -} - -.diag-table tr:nth-child(even) { - background: rgba(0,255,65,.025); -} - -/* Value colour classes */ -.diag-val-good { color: var(--green); } -.diag-val-warn { color: var(--amber); } -.diag-val-bad { color: var(--red); } - -/* SFP power bar */ -.diag-power-bar-wrap { - position: relative; - display: inline-block; - width: 60px; - height: 7px; - background: var(--bg3); - border: 1px solid var(--border); - vertical-align: middle; - margin-left: 6px; - overflow: visible; -} - -.diag-power-bar { - display: inline-block; - position: absolute; - left: 0; - top: 0; - height: 100%; -} - -.diag-power-bar.diag-val-good { background: var(--green); } -.diag-power-bar.diag-val-warn { background: var(--amber); } -.diag-power-bar.diag-val-bad { background: var(--red); } - -.diag-power-zone-warn, -.diag-power-zone-crit { - position: absolute; - top: -2px; - width: 1px; - height: calc(100% + 4px); - pointer-events: none; -} - -.diag-power-zone-warn { background: var(--amber); opacity: .7; } -.diag-power-zone-crit { background: var(--red); opacity: .7; } - -/* ethtool -S stat table */ -.diag-stat-table { - width: 100%; - border-collapse: collapse; - font-size: .58em; -} - -.diag-stat-table td { - padding: 2px 8px; -} - -.diag-stat-table td:first-child { color: var(--text-muted); } -.diag-stat-table td:last-child { color: var(--text-dim); text-align: right; } - -.diag-stat-nonzero-warn { - background: var(--amber-dim); -} - -.diag-stat-nonzero-warn td { color: var(--amber); } - -/* dmesg */ -.diag-dmesg-wrap { - max-height: 200px; - overflow-y: auto; - padding: 6px 8px; -} - -.diag-dmesg-line { - font-family: var(--font); - font-size: .58em; - white-space: pre-wrap; - word-break: break-all; - padding: 1px 0; - color: var(--text-dim); -} - -.diag-dmesg-warn { color: var(--amber); } -.diag-dmesg-err { color: var(--red); } - -/* Pulse link */ -.diag-pulse-link { - font-size: .62em; - padding: 4px 0; - text-align: right; -} - -.diag-pulse-link a { - color: var(--cyan); - text-decoration: none; -} - -.diag-pulse-link a:hover { - text-shadow: var(--glow-cyan); -} - -/* โ”€โ”€ Topology v2 โ€“ professional network diagram โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ - -/* Outer wrapper */ +/* topo-v2 outer wrapper */ .topo-v2 { display: flex; flex-direction: column; @@ -1544,10 +280,8 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } gap: 0; min-width: 860px; padding: 20px 24px 24px; - position: relative; } -/* Each tier row */ .topo-tier { display: flex; justify-content: center; @@ -1555,7 +289,6 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } width: 100%; } -/* โ”€โ”€ Vertical connector section between tiers โ”€โ”€ */ .topo-vc { display: flex; justify-content: center; @@ -1564,8 +297,6 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } position: relative; height: 40px; } - -/* Single centered vertical wire */ .topo-vc-wire { position: absolute; left: 50%; @@ -1576,8 +307,6 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } background: linear-gradient(to bottom, var(--cyan), var(--green)); opacity: .7; } - -/* Labeled vertical connector */ .topo-vc-label { position: absolute; left: calc(50% + 6px); @@ -1585,27 +314,18 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } transform: translateY(-50%); font-size: .58em; color: var(--amber); - text-shadow: var(--glow-amber); white-space: nowrap; letter-spacing: .06em; font-family: var(--font); } -/* โ”€โ”€ WAN tier node (Internet + Router side by side) โ”€โ”€ */ -.topo-tier-wan { - gap: 0; - flex-direction: column; - align-items: center; -} - -/* โ”€โ”€ Individual node boxes โ”€โ”€ */ .topo-v2-node { display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 8px 16px; - border: 1px solid var(--border); + border: 1px solid var(--border-color); background: var(--bg3); position: relative; font-size: .75em; @@ -1614,146 +334,51 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } text-align: center; transition: border-color .2s, box-shadow .2s; } -.topo-v2-node::before { content:'โ”Œ'; position:absolute; top:-1px; left:-1px; color:var(--green); font-size:.85rem; line-height:1; pointer-events:none; } -.topo-v2-node::after { content:'โ”'; position:absolute; top:-1px; right:-1px; color:var(--green); font-size:.85rem; line-height:1; pointer-events:none; } +.topo-v2-icon { font-size: 1.3em; line-height: 1; } +.topo-v2-label { font-weight: bold; letter-spacing: .04em; } +.topo-v2-sub { font-size: .58em; color: var(--text-muted); letter-spacing: .02em; } +.topo-v2-vlan { font-size: .54em; color: var(--cyan); opacity: .75; } -.topo-v2-icon { font-size:1.3em; line-height:1; } -.topo-v2-label { font-weight:bold; letter-spacing:.04em; } -.topo-v2-sub { font-size:.58em; color:var(--text-muted); letter-spacing:.02em; } -.topo-v2-vlan { font-size:.54em; color:var(--cyan); opacity:.75; letter-spacing:.02em; } +.topo-v2-internet { border-color: var(--cyan); color: var(--cyan); text-shadow: var(--glow-cyan); } +.topo-v2-router { border-color: var(--cyan); color: var(--cyan); text-shadow: var(--glow-cyan); } +.topo-v2-switch { border-color: var(--amber); color: var(--amber); text-shadow: var(--glow-amber); } +.topo-v2-host { border-color: var(--border-color); color: var(--text); cursor: default; } -/* Node type colours */ -.topo-v2-internet { border-color:var(--cyan); color:var(--cyan); text-shadow:var(--glow-cyan); } -.topo-v2-router { border-color:var(--cyan); color:var(--cyan); text-shadow:var(--glow-cyan); } -.topo-v2-switch { border-color:var(--amber); color:var(--amber); text-shadow:var(--glow-amber); } -.topo-v2-host { border-color:var(--border); color:var(--text); cursor:default; } +.topo-v2-status-up { border-color: var(--green); box-shadow: 0 0 8px rgba(0,255,136,.2); } +.topo-v2-status-down { border-color: var(--red); box-shadow: 0 0 8px rgba(255,45,85,.35); animation: pulse-glow 2s infinite; } +.topo-v2-status-degraded { border-color: var(--orange); box-shadow: 0 0 8px rgba(255,107,0,.2); } +.topo-v2-status-unknown { border-color: var(--border-color); } +.topo-v2-offrack { border-style: dashed !important; } -/* โ”€โ”€ CSS fork: UDM-Pro โ†’ two switches, no SVG distortion โ”€โ”€ */ -/* The fork sits between the router tier and the switch row. - Drops are at left:25% and left:75%, matching each switch's - centre (each switch lives in a 50%-wide half). */ -.topo-fork { - position: relative; - width: 100%; - height: 40px; - flex-shrink: 0; -} -/* Vertical stem down from router centre */ -.topo-fork-stem { - position: absolute; - left: 50%; - top: 0; - width: 2px; - height: 50%; - transform: translateX(-50%); - background: var(--amber); - opacity: .65; -} -/* Horizontal bar at mid-height, spanning between the two drop points */ -.topo-fork-bar { - position: absolute; - left: 25%; - right: 25%; - top: calc(50% - 1px); - height: 2px; - background: var(--amber); - opacity: .55; +.topo-badge { font-size: .68em; padding: 1px 5px; border: 1px solid; letter-spacing: .03em; } +.topo-badge-up { color: var(--green); border-color: var(--green); text-shadow: var(--glow); } +.topo-badge-down { color: var(--red); border-color: var(--red); animation: pulse-glow 1.5s infinite; } +.topo-badge-degraded { color: var(--orange); border-color: var(--orange); } +.topo-badge-unknown { color: var(--text-muted); border-color: var(--border-color); } + +/* Host wrap + dual-homing wires */ +.topo-v2-host-wrap { display: flex; - justify-content: center; -} -.topo-fork-label { - position: absolute; - top: -13px; - font-size: .54em; - color: var(--amber); - white-space: nowrap; - letter-spacing: .06em; - font-family: var(--font); - opacity: .85; - background: var(--bg); - padding: 0 4px; - text-shadow: var(--glow-amber); -} -/* Left and right vertical drops from bar down to switch tops */ -.topo-fork-drop { - position: absolute; - top: 50%; - width: 2px; - height: 50%; - background: var(--amber); - opacity: .55; -} -.topo-fork-drop-l { left: 25%; transform: translateX(-50%); } -.topo-fork-drop-r { left: 75%; transform: translateX(-50%); } - -/* โ”€โ”€ Switch row: two equal 50% halves โ”€โ”€ */ -/* Each switch is centred in its half, so their centres are at - exactly 25% and 75% โ€” matching the fork drops above. */ -.topo-sw-row { - display: flex; - width: 100%; - position: relative; + flex-direction: column; align-items: center; } -.topo-sw-half { - width: 50%; +.topo-v2-host-wires { display: flex; - justify-content: center; - padding: 0 16px; - position: relative; - z-index: 1; /* sit above the ISL line */ -} -/* ISL line rendered as ::before โ€” switch boxes (bg3) cover it at their edges */ -.topo-sw-row::before { - content: ''; - position: absolute; - left: 25%; - right: 25%; - top: 50%; - height: 2px; - background: var(--amber); - opacity: .35; - z-index: 0; -} -/* ISL label centred between the two switches */ -.topo-sw-row::after { - content: '10G ISL'; - position: absolute; - left: 50%; - transform: translateX(-50%); - top: calc(50% - 14px); - font-size: .5em; - color: var(--amber); - white-space: nowrap; - font-family: var(--font); - letter-spacing: .06em; - opacity: .65; - background: var(--bg); - padding: 0 5px; - z-index: 2; + gap: 6px; + height: 28px; + align-items: flex-start; } +.topo-v2-wire-10g { width: 2px; height: 100%; background: var(--green); opacity: .55; } +.topo-v2-wire-1g { width: 0; height: 100%; border-left: 2px dashed var(--amber); opacity: .45; } -/* โ”€โ”€ Dual-home bus section โ”€โ”€ */ -/* This is the complex section linking two switches to N hosts */ +/* Bus rails */ .topo-bus-section { width: 100%; display: flex; flex-direction: column; align-items: stretch; gap: 0; - margin-top: 0; } - -/* Bus bar row: the horizontal rail that distributes to hosts */ -.topo-bus-bars { - display: flex; - flex-direction: column; - align-items: stretch; - position: relative; - width: 100%; -} - -/* The two drop buses: 10G (green) and 1G mgmt (amber dashed) */ .topo-bus-10g { display: flex; align-items: center; @@ -1770,14 +395,12 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } .topo-bus-10g-label { font-size: .56em; color: var(--green); - text-shadow: var(--glow); white-space: nowrap; letter-spacing: .05em; font-family: var(--font); opacity: .85; padding: 0 8px; } - .topo-bus-1g { display: flex; align-items: center; @@ -1794,7 +417,6 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } .topo-bus-1g-label { font-size: .56em; color: var(--amber); - text-shadow: var(--glow-amber); white-space: nowrap; letter-spacing: .05em; font-family: var(--font); @@ -1802,7 +424,7 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } padding: 0 8px; } -/* โ”€โ”€ Host row โ”€โ”€ */ +/* Hosts row */ .topo-v2-hosts { display: flex; justify-content: center; @@ -1812,23 +434,14 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } width: 100%; } -/* Host status colouring */ -.topo-v2-status-up { border-color:var(--green); box-shadow:0 0 8px rgba(0,255,65,.2); } -.topo-v2-status-down { border-color:var(--red); box-shadow:0 0 8px rgba(255,68,68,.35); animation:pulse-glow 2s infinite; } -.topo-v2-status-degraded{ border-color:var(--orange); box-shadow:0 0 8px rgba(255,140,0,.2); } -.topo-v2-status-unknown { border-color:var(--border); } - -/* Off-rack host: dashed border */ -.topo-v2-offrack { border-style: dashed !important; } - -/* โ”€โ”€ Legend row โ”€โ”€ */ +/* Topology legend */ .topo-legend { display: flex; gap: 18px; align-items: center; margin-top: 14px; padding-top: 10px; - border-top: 1px solid rgba(0,255,65,.12); + border-top: 1px solid rgba(255,107,0,.12); flex-wrap: wrap; justify-content: center; } @@ -1840,106 +453,290 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } color: var(--text-muted); font-family: var(--font); } -.topo-legend-line-10g { - width: 24px; height: 2px; - background: var(--green); - display: inline-block; +.topo-legend-line-10g { width: 24px; height: 2px; background: var(--green); display: inline-block; } +.topo-legend-line-1g { width: 24px; height: 0; border-top: 2px dashed var(--amber); display: inline-block; } +.topo-legend-line-wan { width: 24px; height: 2px; background: linear-gradient(to right, var(--cyan), var(--green)); display: inline-block; } + +/* โ”€โ”€ Link debug page โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.link-host-list { display: flex; flex-direction: column; gap: 18px; } +.link-host-panel { + background: var(--bg2); + border: 1px solid var(--border-color); } -.topo-legend-line-1g { - width: 24px; height: 0; - border-top: 2px dashed var(--amber); - display: inline-block; +.link-host-title { + display: flex; + align-items: center; + gap: 12px; + padding: 9px 16px; + background: var(--bg3); + border-bottom: 1px solid var(--border-color); + cursor: pointer; + user-select: none; } -.topo-legend-line-isl { - width: 24px; height: 2px; - background: var(--amber); - display: inline-block; +.link-host-title:hover { background: var(--bg-hover); } +.link-host-name { font-weight: bold; font-size: .88em; color: var(--amber); letter-spacing: .05em; } +.link-host-ip { font-size: .72em; color: var(--text-muted); } +.link-host-upd { font-size: .65em; color: var(--text-muted); margin-left: auto; } +.panel-toggle { font-size: .65em; color: var(--text-muted); flex-shrink: 0; margin-left: 6px; padding: 0 4px; border: 1px solid var(--border-color); } +.link-host-panel.collapsed > .link-ifaces-grid { display: none; } + +.link-collapse-bar { display: flex; gap: 8px; margin-bottom: 10px; } + +.link-ifaces-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); } -.topo-legend-line-wan { - width: 24px; height: 2px; - background: linear-gradient(to right, var(--cyan), var(--green)); +.link-iface-card { + border-right: 1px solid rgba(255,107,0,.10); + border-bottom: 1px solid rgba(255,107,0,.10); + padding: 12px 14px; +} +.link-iface-card:last-child { border-right: none; } +.link-iface-card.port-down { opacity: .42; filter: saturate(.3); } + +.link-iface-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; + padding-bottom: 6px; + border-bottom: 1px solid rgba(255,107,0,.10); +} +.link-iface-name { font-weight: bold; font-size: .84em; color: var(--amber); flex: 1; } +.link-iface-speed { font-size: .75em; color: var(--cyan); font-weight: bold; } +.link-iface-type { font-size: .65em; color: var(--text-muted); padding: 1px 5px; border: 1px solid var(--text-muted); letter-spacing: .04em; } +.link-iface-type.type-fibre { color: var(--cyan); border-color: var(--cyan-dim); } +.link-iface-type.type-copper{ color: var(--green); border-color: var(--green-muted); } +.link-iface-type.type-da { color: var(--amber); border-color: var(--amber-dim); } + +.link-stats-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px 16px; + margin-bottom: 10px; +} +.link-stat { display: flex; flex-direction: column; gap: 1px; } +.link-stat-label { font-size: .6em; color: var(--text-muted); text-transform: uppercase; letter-spacing: .07em; } +.link-stat-value { font-size: .78em; font-weight: bold; color: var(--text-dim); } + +.val-good { color: var(--green); text-shadow: var(--glow); } +.val-warn { color: var(--orange); } +.val-crit { color: var(--red); text-shadow: var(--glow-red); } +.val-neutral { color: var(--text-muted); } +.val-cyan { color: var(--cyan); text-shadow: var(--glow-cyan); } +.val-amber { color: var(--amber); text-shadow: var(--glow-amber); } +.counter-zero { color: var(--green); } +.counter-nonzero { color: var(--red); text-shadow: var(--glow-red); } + +/* Traffic bars */ +.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-value { font-size: .7em; color: var(--text-dim); width: 68px; text-align: right; flex-shrink: 0; } + +/* SFP / optical panel */ +.sfp-panel { + margin-top: 10px; + padding: 10px 10px 8px; + background: var(--bg3); + border: 1px solid rgba(0,212,255,.2); + position: relative; +} +.sfp-panel::before { + content: '[ SFP / OPTICAL ]'; + position: absolute; + top: -8px; left: 10px; + font-size: .6em; + color: var(--cyan); + background: var(--bg3); + padding: 0 4px; + letter-spacing: .09em; + font-weight: bold; +} +.sfp-vendor-row { font-size: .7em; color: var(--text-muted); margin-bottom: 8px; } +.sfp-vendor-row span { color: var(--text-dim); } +.sfp-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px 12px; +} +.sfp-stat { display: flex; flex-direction: column; gap: 1px; } +.sfp-stat-label { font-size: .58em; color: var(--text-muted); text-transform: uppercase; letter-spacing: .07em; } +.sfp-stat-value { font-size: .78em; font-weight: bold; } +.power-row { display: flex; align-items: center; gap: 5px; margin-top: 1px; } +.power-track { flex: 1; height: 3px; background: var(--bg-primary); border: 1px solid rgba(255,107,0,.15); position: relative; overflow: hidden; } +.power-fill { height: 100%; position: absolute; left: 0; top: 0; transition: width .4s; } +.power-ok { background: var(--green); box-shadow: 0 0 3px var(--green); } +.power-warn { background: var(--orange); } +.power-crit { background: var(--red); box-shadow: 0 0 3px var(--red); } + +/* Link alert badges + PoE bars */ +.link-alert-badge { display: inline-block; + font-size: .6em; + font-weight: bold; + padding: 1px 5px; + background: var(--red-dim); + color: var(--red); + border: 1px solid var(--red); + margin-left: 4px; + vertical-align: middle; + letter-spacing: .05em; +} +.link-alert-badge.link-alert-amber { background: var(--amber-dim); color: var(--amber); border-color: var(--amber); } +.port-badge { font-size: .58em; padding: 1px 5px; border: 1px solid; letter-spacing: .05em; font-weight: bold; vertical-align: middle; } +.port-badge-uplink{ color: var(--amber); border-color: var(--amber-dim); } +.port-badge-poe { color: var(--cyan); border-color: var(--cyan-dim); } +.port-badge-num { color: var(--text-muted); border-color: rgba(255,107,0,.2); } +.port-lldp { font-size: .68em; color: var(--cyan); margin: -4px 0 6px; letter-spacing: .02em; } +.port-poe-info { font-size: .68em; color: var(--amber); margin: -4px 0 6px; } +.poe-bar-track { height: 3px; background: var(--bg3); margin-top: 3px; overflow: hidden; } +.poe-bar-fill { height: 100%; transition: width .4s; } +.poe-bar-ok { background: var(--green); } +.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); } -/* โ”€โ”€ Drop-wire stubs for host dual-homing โ”€โ”€ */ -/* Wrapper that sits above each host showing its two connections */ -.topo-v2-host-wrap { +/* Link health summary */ +.link-summary-panel { + background: var(--bg2); + border: 1px solid var(--border-color); + padding: 12px 16px; + margin-bottom: 12px; +} +.link-summary-panel.link-summary-has-alerts { border-color: var(--amber); } +.link-summary-grid { display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-end; } +.link-summary-stat { min-width: 80px; } +.link-summary-stat.lss-alert .lss-label { color: var(--amber); } +.lss-label { display: block; font-size: .62em; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; margin-bottom: 2px; } +.lss-value { font-size: 1.2em; font-weight: bold; color: var(--text); } +.lss-sub { font-size: .7em; color: var(--text-muted); font-weight: normal; } + +.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; } + +/* โ”€โ”€ Inspector page โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ +.inspector-layout { + display: flex; + gap: 16px; + align-items: flex-start; + min-height: 300px; +} +.inspector-main { + flex: 1; + min-width: 0; display: flex; flex-direction: column; - align-items: center; - gap: 0; + gap: 14px; } - -.topo-v2-host-wires { +.inspector-chassis { + background: var(--bg2); + border: 1px solid var(--border-color); +} +.chassis-header { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 16px; + background: var(--bg3); + border-bottom: 1px solid var(--border-color); +} +.chassis-name { font-weight: bold; font-size: .88em; color: var(--amber); letter-spacing: .05em; } +.chassis-ip { font-size: .72em; color: var(--text-muted); } +.chassis-meta { font-size: .65em; color: var(--text-muted); margin-left: auto; } +.chassis-body { padding: 12px 16px 14px; position: relative; } +.chassis-rows { display: flex; flex-direction: column; gap: 5px; margin-bottom: 8px; } +.chassis-row { display: flex; flex-wrap: wrap; gap: 4px; } +.chassis-sfp-section { display: flex; gap: 6px; - height: 28px; - align-items: flex-start; + padding-top: 8px; + border-top: 1px solid rgba(0,212,255,.15); + margin-top: 4px; } -.topo-v2-wire-10g { - width: 2px; - height: 100%; - background: var(--green); - opacity: .55; -} -.topo-v2-wire-1g { - width: 0; - height: 100%; - border-left: 2px dashed var(--amber); - opacity: .45; -} - -/* host badge */ -.topo-v2-badge { - font-size: .65em; - padding: 1px 5px; - border: 1px solid; - letter-spacing: .03em; - margin-top: 2px; -} -.topo-v2-badge-up { color:var(--green); border-color:var(--green); text-shadow:var(--glow); } -.topo-v2-badge-down { color:var(--red); border-color:var(--red); animation:pulse-glow 1.5s infinite; } -.topo-v2-badge-degraded{ color:var(--orange); border-color:var(--orange); } -.topo-v2-badge-unknown { color:var(--text-muted); border-color:var(--border); } - -/* (removed: old SVG fork โ€” replaced by .topo-fork CSS above) */ - -/* โ”€โ”€ Drop wires from each switch down to the bus rails โ”€โ”€ */ -.topo-sw-drops { - position: relative; - width: 100%; - height: 20px; - flex-shrink: 0; -} -.topo-sw-drop { +.chassis-ear-l, .chassis-ear-r { position: absolute; - top: 0; - width: 2px; - height: 100%; - opacity: .5; + top: 50%; + transform: translateY(-50%); + width: 8px; height: 36px; + background: var(--bg3); + border: 1px solid var(--border-color); } -.topo-sw-drop-l { - left: 25%; - transform: translateX(-50%); - background: var(--green); -} -.topo-sw-drop-r { - left: 75%; - transform: translateX(-50%); - background: var(--amber); - border-left: 2px dashed var(--amber); - width: 0; - background: none; +.chassis-ear-l { left: -9px; border-right: none; } +.chassis-ear-r { right: -9px; border-left: none; } +.chassis-ear-l::before, .chassis-ear-r::before { + content: ''; + position: absolute; + top: 50%; left: 50%; + transform: translate(-50%, -50%); + width: 4px; height: 4px; + border-radius: 50%; + background: var(--border-color); } -/* โ”€โ”€ Improved chassis legend โ”€โ”€ */ +/* Port blocks */ +.switch-port-block { + width: 34px; height: 34px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 1px; + padding: 2px 1px; + font-size: .6em; + font-weight: bold; + border: 1px solid; + cursor: pointer; + transition: box-shadow .1s, border-color .1s, background .1s; + user-select: none; + flex-shrink: 0; + letter-spacing: 0; +} +.switch-port-block.sfp-port { width: 34px; height: 40px; font-size: .55em; border-left-width: 2px; } +.switch-port-block.sfp-block { width: 36px; height: 38px; font-size: .55em; letter-spacing: .04em; border-left-width: 3px; } +.switch-port-block.down { background: var(--bg3); border-color: rgba(255,107,0,.12); color: rgba(255,107,0,.2); } +.switch-port-block.up { background: rgba(0,255,136,.06); border-color: var(--green-muted); color: var(--green); text-shadow: 0 0 4px rgba(0,255,136,.5); } +.switch-port-block.up:hover{ background: rgba(0,255,136,.13); border-color: var(--green); box-shadow: var(--glow); } +.switch-port-block.poe-active { background: var(--amber-dim); border-color: var(--amber); color: var(--amber); } +.switch-port-block.poe-active:hover { box-shadow: var(--glow-amber); } +.switch-port-block.uplink { background: var(--cyan-dim); border-color: var(--cyan); color: var(--cyan); } +.switch-port-block.uplink:hover { box-shadow: var(--glow-cyan); } +.switch-port-block.selected { outline: 2px solid #fff; outline-offset: 1px; } +.port-num { line-height: 1; font-weight: bold; } +.port-speed { font-size: .72em; opacity: .7; line-height: 1; font-weight: normal; } +.port-lldp { font-size: .62em; opacity: .65; line-height: 1; max-width: 32px; overflow: hidden; white-space: nowrap; text-overflow: clip; font-weight: normal; } +.chassis-row.us24pro-row .switch-port-block:nth-child(2n+1):not(:first-child) { margin-left: 6px; } + +/* Chassis legend */ .chassis-legend { display: flex; gap: 16px; align-items: center; padding: 7px 16px 8px; - border-top: 1px solid rgba(0,255,65,.1); - background: var(--bg2); + border-top: 1px solid rgba(255,107,0,.1); flex-wrap: wrap; } .chassis-legend-item { @@ -1952,266 +749,163 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); } letter-spacing: .04em; text-transform: uppercase; } -.chassis-legend-swatch { - width: 14px; - height: 14px; - border: 1px solid; - flex-shrink: 0; - display: inline-block; -} -.cls-down { background:var(--bg3); border-color:rgba(0,255,65,.15); } -.cls-up { background:rgba(0,255,65,.06); border-color:var(--green-muted); } -.cls-poe { background:var(--amber-dim); border-color:var(--amber); } -.cls-uplink { background:var(--cyan-dim); border-color:var(--cyan); } +.chassis-legend-swatch { width: 14px; height: 14px; border: 1px solid; flex-shrink: 0; display: inline-block; } +.cls-down { background: var(--bg3); border-color: rgba(255,107,0,.12); } +.cls-up { background: rgba(0,255,136,.06); border-color: var(--green-muted); } +.cls-poe { background: var(--amber-dim); border-color: var(--amber); } +.cls-uplink { background: var(--cyan-dim); border-color: var(--cyan); } -/* โ”€โ”€ Port block v2: flex-col with speed sub-label โ”€โ”€ */ -.switch-port-block { - flex-direction: column; - justify-content: center; - align-items: center; - gap: 1px; - padding: 2px 1px; -} -.port-num { - line-height: 1; - font-weight: bold; -} -.port-speed { - font-size: .72em; - opacity: .7; - line-height: 1; - font-weight: normal; -} -.port-lldp { - font-size: .62em; - opacity: .65; - line-height: 1; - max-width: 32px; +/* Inspector detail panel */ +.inspector-panel { + width: 0; overflow: hidden; - white-space: nowrap; - text-overflow: clip; - font-weight: normal; + flex-shrink: 0; + transition: width .2s ease; } - -/* US24PRO port group separators (every 2 ports = 1 pair gap) */ -.chassis-row.us24pro-row .switch-port-block:nth-child(2n+1):not(:first-child) { - margin-left: 6px; +.inspector-panel.open { width: 310px; } +.inspector-panel-inner { + width: 310px; + background: var(--bg2); + border: 1px solid var(--border-color); + padding: 14px 14px 18px; + overflow-y: auto; + max-height: calc(100vh - 120px); } +.panel-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 12px; + padding-bottom: 10px; + border-bottom: 1px solid var(--border-color); +} +.panel-port-name { font-weight: bold; font-size: .92em; color: var(--amber); } +.panel-meta { font-size: .68em; color: var(--text-muted); margin-top: 2px; } +.panel-close { + background: none; + border: 1px solid var(--border-color); + color: var(--text-muted); + cursor: pointer; + font-size: .8em; + padding: 1px 7px; + font-family: var(--font); + flex-shrink: 0; + transition: all .15s; +} +.panel-close:hover { color: var(--red); border-color: var(--red); } +.panel-section-title { + font-size: .62em; + font-weight: bold; + color: var(--amber); + text-transform: uppercase; + letter-spacing: .1em; + margin: 10px 0 5px; + padding-bottom: 3px; + border-bottom: 1px solid rgba(255,107,0,.12); +} +.panel-section-title:first-of-type { margin-top: 0; } +.panel-row { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 2px 0; +} +.panel-label { font-size: .68em; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; flex-shrink: 0; } +.panel-val { font-size: .75em; font-weight: bold; color: var(--text-dim); text-align: right; word-break: break-all; } -/* SFP block: taller and narrower cage look */ -.switch-port-block.sfp-block { - width: 36px; - height: 38px; - font-size: .55em; +/* Path debug */ +.path-conn-type { font-size: .68em; color: var(--cyan); font-weight: normal; margin-left: 6px; text-transform: none; letter-spacing: normal; } +.path-mismatch-alert{ background: var(--amber-dim); border-left: 3px solid var(--amber); color: var(--amber); padding: 4px 8px; margin-bottom: 6px; font-size: .72em; } +.path-debug-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 6px; } +.path-col { background: var(--bg3); border: 1px solid rgba(255,107,0,.15); padding: 7px 8px; } +.path-col-header { font-size: .62em; font-weight: bold; color: var(--amber); margin-bottom: 5px; padding-bottom: 3px; border-bottom: 1px solid rgba(255,107,0,.12); letter-spacing: .04em; } +.path-row { display: flex; justify-content: space-between; gap: 4px; font-size: .65em; padding: 1px 0; } +.path-row span:first-child { color: var(--text-muted); flex-shrink: 0; } +.path-row span:last-child { color: var(--text-dim); font-weight: bold; text-align: right; word-break: break-all; } +.path-dom { margin-top: 5px; padding-top: 5px; border-top: 1px solid rgba(0,212,255,.15); } +.path-dom-row { display: flex; justify-content: space-between; font-size: .65em; padding: 1px 0; color: var(--cyan); } +.path-dom-row span:first-child { color: var(--text-muted); } + +/* Link diagnostics */ +.diag-bar { display: flex; align-items: center; gap: 10px; margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--border-color); } +.btn-diag { + font-family: var(--font); + font-size: .65em; + color: var(--cyan); + background: transparent; + border: 1px solid var(--cyan); + padding: 4px 10px; + cursor: pointer; letter-spacing: .04em; - border-left-width: 3px; + transition: background .15s, box-shadow .15s; + animation: diag-pulse 2.5s ease-in-out infinite; } -/* SFP port in rows โ€” same width as copper ports so all-SFP switches - (e.g. USW-Agg / USL8A) don't appear narrower than other switches */ -.switch-port-block.sfp-port { - width: 34px; - height: 40px; - font-size: .55em; - border-left-width: 2px; -} - -/* Chassis mounting ears */ -.chassis-body { +.btn-diag:hover { background: var(--cyan-dim); box-shadow: var(--glow-cyan); } +.diag-status { font-size: .6em; color: var(--text-muted); font-style: italic; } +.diag-error { color: var(--red); font-size: .65em; margin-top: 8px; } +.diag-results { margin-top: 4px; } +.diag-results-inner { display: flex; flex-direction: column; gap: 6px; } +.diag-health-banner { display: flex; gap: 8px; padding: 6px 0 4px; margin-bottom: 2px; } +.diag-health-critical{ background: var(--red-dim); color: var(--red); border: 1px solid var(--red); padding: 2px 8px; font-size: .62em; font-weight: bold; letter-spacing: .05em; } +.diag-health-warning { background: var(--amber-dim); color: var(--amber); border: 1px solid var(--amber); padding: 2px 8px; font-size: .62em; font-weight: bold; letter-spacing: .05em; } +.diag-health-ok { background: var(--green-dim); color: var(--green); border: 1px solid var(--green); padding: 2px 8px; font-size: .62em; font-weight: bold; letter-spacing: .05em; } +.diag-issue-list { display: flex; flex-direction: column; gap: 3px; } +.diag-issue-row { font-size: .62em; padding: 3px 6px; background: var(--bg2); border-left: 2px solid var(--border-color); line-height: 1.4; } +.diag-code { font-weight: bold; color: var(--amber); } +.diag-section { background: var(--bg2); border: 1px solid rgba(255,107,0,.10); } +.diag-section-header { font-size: .62em; font-weight: bold; color: var(--amber); padding: 4px 8px; letter-spacing: .04em; border-bottom: 1px solid rgba(255,107,0,.10); background: rgba(255,107,0,.03); } +.diag-collapsible .diag-section-body { display: none; } +.diag-collapsible.diag-open .diag-section-body { display: block; } +.diag-toggle { cursor: pointer; user-select: none; } +.diag-toggle-hint { font-weight: normal; color: var(--text-muted); font-size: .9em; } +.diag-table { width: 100%; border-collapse: collapse; font-size: .62em; } +.diag-table td { padding: 3px 8px; vertical-align: top; } +.diag-table td:first-child { color: var(--text-muted); width: 40%; white-space: nowrap; } +.diag-table td:last-child { color: var(--text-dim); font-weight: bold; word-break: break-all; } +.diag-table tr:nth-child(even) { background: rgba(255,107,0,.02); } +.diag-val-good { color: var(--green); } +.diag-val-warn { color: var(--amber); } +.diag-val-bad { color: var(--red); } +.diag-power-bar-wrap { position: relative; -} -.chassis-ear-l, -.chassis-ear-r { - position: absolute; - top: 50%; - transform: translateY(-50%); - width: 8px; - height: 36px; + display: inline-block; + width: 60px; height: 7px; background: var(--bg3); - border: 1px solid var(--border); -} -.chassis-ear-l { left: -9px; border-right: none; } -.chassis-ear-r { right: -9px; border-left: none; } -.chassis-ear-l::before, -.chassis-ear-r::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 4px; - height: 4px; - border-radius: 50%; - background: var(--border); + border: 1px solid var(--border-color); + vertical-align: middle; + margin-left: 6px; + overflow: visible; } +.diag-power-bar { display: inline-block; position: absolute; left: 0; top: 0; height: 100%; } +.diag-power-bar.diag-val-good { background: var(--green); } +.diag-power-bar.diag-val-warn { background: var(--amber); } +.diag-power-bar.diag-val-bad { background: var(--red); } +.diag-power-zone-warn, .diag-power-zone-crit { position: absolute; top: -2px; width: 1px; height: calc(100% + 4px); pointer-events: none; } +.diag-power-zone-warn { background: var(--amber); opacity: .7; } +.diag-power-zone-crit { background: var(--red); opacity: .7; } +.diag-stat-table { width: 100%; border-collapse: collapse; font-size: .58em; } +.diag-stat-table td { padding: 2px 8px; } +.diag-stat-table td:first-child { color: var(--text-muted); } +.diag-stat-table td:last-child { color: var(--text-dim); text-align: right; } +.diag-stat-nonzero-warn td { color: var(--amber); } +.diag-stat-nonzero-warn { background: var(--amber-dim); } +.diag-dmesg-wrap { max-height: 200px; overflow-y: auto; padding: 6px 8px; } +.diag-dmesg-line { font-family: var(--font); font-size: .58em; white-space: pre-wrap; word-break: break-all; padding: 1px 0; color: var(--text-dim); } +.diag-dmesg-warn { color: var(--amber); } +.diag-dmesg-err { color: var(--red); } +.diag-pulse-link { font-size: .62em; padding: 4px 0; text-align: right; } +.diag-pulse-link a { color: var(--cyan); } +.diag-pulse-link a:hover { text-shadow: var(--glow-cyan); } /* โ”€โ”€ Responsive โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ @media (max-width: 768px) { - .host-grid { grid-template-columns:1fr; } - .topology { display:none; } - .form-row { flex-direction:column; } - .status-bar { flex-direction:column; align-items:flex-start; } - .link-ifaces-grid { grid-template-columns:1fr; } - .sfp-grid { grid-template-columns:1fr 1fr; } - .header-nav { display:none; } - .inspector-layout { flex-direction:column; } - .inspector-panel.open { width:100%; } - .inspector-panel-inner { width:100%; } -} - -/* โ”€โ”€ 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: 12px 0 0; - font-size: 0.88em; - font-family: var(--font); - border-radius: 2px; -} - -/* โ”€โ”€ Link alert badges (error/flap indicators) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.link-alert-badge { - display: inline-block; - font-size: .6em; - font-weight: bold; - padding: 1px 5px; - border-radius: 2px; - background: var(--red-dim); - color: var(--red); - border: 1px solid var(--red); - margin-left: 4px; - vertical-align: middle; - letter-spacing: .05em; -} - -.link-alert-badge.link-alert-amber { - background: var(--amber-dim); - color: var(--amber); - border-color: var(--amber); -} - -/* โ”€โ”€ PoE utilisation bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.poe-bar-track { - height: 3px; - background: var(--bg3); - border-radius: 2px; - margin-top: 3px; - overflow: hidden; -} - -.poe-bar-fill { - height: 100%; - border-radius: 2px; - transition: width 0.4s ease; -} - -.poe-bar-ok { background: var(--green); } -.poe-bar-warn { background: var(--amber); } -.poe-bar-crit { background: var(--red); } - -/* โ”€โ”€ Path mismatch alert โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.path-mismatch-alert { - background: var(--amber-dim); - border-left: 3px solid var(--amber); - color: var(--amber); - padding: 4px 8px; - margin-bottom: 6px; - font-size: .72em; - border-radius: 2px; -} - -/* โ”€โ”€ Error state for data containers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.error-state { - padding: 16px 20px; - border-left: 3px solid var(--red); - background: var(--red-dim); - color: var(--red); - border-radius: 2px; - font-size: .88em; -} - -/* โ”€โ”€ Link health summary panel โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.link-summary-panel { - background: var(--bg2); - border: 1px solid var(--border); - border-radius: 2px; - padding: 12px 16px; - margin-bottom: 12px; -} - -.link-summary-panel.link-summary-has-alerts { - border-color: var(--amber); -} - -.link-summary-grid { - display: flex; - flex-wrap: wrap; - gap: 20px; - align-items: flex-end; -} - -.link-summary-stat { - min-width: 80px; -} - -.link-summary-stat.lss-alert .lss-label { - color: var(--amber); -} - -.lss-label { - display: block; - font-size: .62em; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: .05em; - margin-bottom: 2px; -} - -.lss-value { - font-size: 1.2em; - font-weight: bold; - color: var(--text); -} - -.lss-sub { - font-size: .7em; - color: var(--text-muted); - font-weight: normal; -} - -/* โ”€โ”€ Recently resolved table โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.row-resolved td { - opacity: 0.75; -} - -.badge-resolved { - background: var(--bg3); - color: var(--text-muted); - border-color: var(--border); - text-decoration: line-through; -} - -.section-badge-resolved { - background: var(--bg3); - color: var(--text-muted); - border: 1px solid var(--border); - font-size: .65em; - padding: 2px 7px; - border-radius: 10px; -} - -/* โ”€โ”€ Pagination notice โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -.pagination-notice { - font-size: .8em; - color: var(--text-muted); - padding: 6px 0 8px; -} -.pagination-notice a { - color: var(--accent); - text-decoration: none; -} -.pagination-notice a:hover { - text-decoration: underline; + .host-grid { grid-template-columns: 1fr; } + .topology { display: none; } + .form-row { flex-direction: column; } + .status-bar { flex-direction: column; align-items: flex-start; } + .link-ifaces-grid { grid-template-columns: 1fr; } + .sfp-grid { grid-template-columns: 1fr 1fr; } + .inspector-layout { flex-direction: column; } + .inspector-panel.open { width: 100%; } + .inspector-panel-inner{ width: 100%; } } diff --git a/templates/base.html b/templates/base.html index 61a27fd..993e92e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,47 +1,52 @@ - + + {% block title %}GANDALF{% endblock %} + + + -
-
-
- GANDALF - Network Monitor // LotusGuild + +
+ -
- {{ user.name or user.username }} +
+ {{ user.name or user.username }}
-
+
{% block content %}{% endblock %}
@@ -51,6 +56,11 @@ }; + {% block scripts %}{% endblock %} diff --git a/templates/index.html b/templates/index.html index 9b6c619..0008949 100644 --- a/templates/index.html +++ b/templates/index.html @@ -18,14 +18,14 @@
{{ last_check }} - +
-
-
-

Network Hosts

+
+
+

Network Hosts

@@ -102,7 +102,7 @@
-
+
@@ -201,7 +201,7 @@ {% endif %} @@ -222,12 +222,13 @@ {% if snapshot.unifi %} -
-
-

UniFi Devices

+
+
+

UniFi Devices

-
-
Active network alerts
SevTypeTargetDetail
+
+
+ @@ -251,7 +252,7 @@
UniFi network devices
Status{{ d.ip }} {% if not d.connected %} -