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}
-
-
+
+
+ Active network alerts
| Sev | Type | Target | Detail |
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 %}
+
+
+
- |