Files
gandalf/static/style.css
T
jared c45dd007d1
Lint / Python (flake8) (push) Failing after 50s
Lint / JS (eslint) (push) Successful in 7s
Test / Python Tests (pytest) (push) Successful in 51s
Lint / Notify on failure (push) Successful in 2s
Lint / Deploy (push) Has been skipped
Security / Python Security (bandit) (push) Failing after 59s
Fix field name mismatches, add events filter, in-place suppression refresh
- links.html: fix all field name bugs (auto_negotiation→autoneg, full_duplex,
  tx/rx_errors/drops_per_sec→_rate, tx/rx_bytes_per_sec→_rate, poe_total_w/poe_max_w
  computed from ports, renderUnifiSwitches uses top-level updated timestamp)
- suppressions.html: in-place DOM refresh after create/remove (no page reload),
  datalist autocomplete for target names, form reset after submit
- inspector.html: ESC key closes detail panel via lt.keys.on
- index.html: events filter bar with search input + severity pills (All/Critical/Warning),
  MutationObserver re-applies filter after dynamic updates
- style.css: g-section-actions, events-filter-bar, sev-pills layout
- app.js/db.py/monitor.py: carry forward prior session fixes (Promise.allSettled,
  daemon_ok, stale connection handling, double Prometheus call, self.cfg fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 23:35:02 -04:00

941 lines
38 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ══════════════════════════════════════════════════════════════════════
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.
══════════════════════════════════════════════════════════════════════ */
/* ── Variable aliases bridging to base.css palette ────────────────── */
:root {
/* 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);
--bg: var(--bg-primary);
--bg2: var(--bg-secondary);
--bg3: var(--bg-tertiary);
--text: var(--text-primary);
--text-dim: var(--text-secondary);
--border: var(--border-color);
--font: var(--font-mono);
--bg-hover: rgba(255,107,0,.06);
/* 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);
/* 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);
}
/* ── Light theme overrides for dim/glow variables ────────────────── */
[data-theme="light"] {
--green-dim: rgba(0,160,80,.08);
--green-muted: rgba(0,160,80,.45);
--amber-dim: rgba(180,120,0,.07);
--cyan-dim: rgba(0,140,180,.08);
--red-dim: rgba(200,30,60,.06);
--orange-dim: rgba(180,80,0,.06);
--glow: none;
--glow-amber: none;
--glow-red: none;
--glow-cyan: none;
--glow-xl: none;
}
/* ── Refresh button loading state ────────────────────────────────── */
[data-action="refresh"].is-loading {
opacity: .5;
pointer-events: none;
cursor: wait;
}
[data-action="refresh"].is-loading::after {
content: '…';
}
/* ── 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 blink { 0%,49%{opacity:1} 50%,100%{opacity:0} }
@keyframes diag-pulse {
0%, 100% { box-shadow: none; }
50% { box-shadow: 0 0 6px rgba(0,212,255,.4); }
}
/* ── 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-color);
padding-bottom: 6px;
}
.g-section-title {
font-size: .88em;
font-weight: bold;
color: var(--text-accent);
text-transform: uppercase;
letter-spacing: .08em;
}
.g-section-badge {
font-size: .72em;
font-weight: bold;
color: var(--accent-red);
border: 1px solid var(--accent-red);
padding: 1px 7px;
}
.g-section-badge-resolved {
font-size: .68em;
color: var(--text-muted);
border: 1px solid var(--border-color);
padding: 1px 7px;
}
.g-section-actions { margin-left: auto; }
.events-filter-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.events-filter-bar .lt-input-sm { width: 220px; }
.sev-pills { display: flex; gap: 4px; }
.g-page-header { margin-bottom: 20px; }
.g-page-title {
font-size: 1em;
font-weight: bold;
color: var(--text-accent);
letter-spacing: .06em;
}
.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 {
display: flex;
align-items: center;
justify-content: space-between;
background: var(--bg2);
border: 1px solid var(--border-color);
padding: 9px 16px;
margin-bottom: 18px;
gap: 12px;
flex-wrap: wrap;
}
.status-chips { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
.chip {
font-size: .78em;
font-weight: bold;
padding: 3px 10px;
border: 1px solid;
letter-spacing: .04em;
}
.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); }
/* ── Stale monitoring banner ──────────────────────────────────────── */
.stale-banner {
background: var(--amber-dim);
border: 1px solid var(--amber);
border-left: 4px solid var(--amber);
color: var(--amber);
padding: 10px 16px;
margin: 0 0 14px;
font-size: .88em;
}
/* ── 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;
}
/* ── Status dots ──────────────────────────────────────────────────── */
.host-status-dot, .iface-dot,
.dot-up, .dot-down, .dot-degraded, .dot-unknown, .dot-initial_down {
display: inline-block;
width: 8px; height: 8px;
border: 1px solid;
flex-shrink: 0;
vertical-align: middle;
}
.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; }
/* ── Host cards ───────────────────────────────────────────────────── */
.host-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
gap: 12px;
margin-top: 14px;
}
.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;
font-weight: bold;
}
.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 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: 6px; }
.pill {
padding: 3px 10px;
border: 1px solid var(--border-color);
background: transparent;
font-family: var(--font);
font-size: .72em;
font-weight: bold;
cursor: pointer;
color: var(--text-muted);
transition: all .15s;
letter-spacing: .04em;
}
.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); }
/* ── 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-color);
padding: 10px;
}
.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); }
/* ── Topology diagram ─────────────────────────────────────────────── */
.topology {
background: var(--bg2);
border: 1px solid var(--border-color);
padding: 20px 16px 16px;
margin-bottom: 16px;
text-align: center;
overflow-x: auto;
}
/* topo-v2 outer wrapper */
.topo-v2 {
display: flex;
flex-direction: column;
align-items: center;
gap: 0;
min-width: 860px;
padding: 20px 24px 24px;
}
.topo-tier {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
.topo-vc {
display: flex;
justify-content: center;
align-items: flex-start;
width: 100%;
position: relative;
height: 40px;
}
.topo-vc-wire {
position: absolute;
left: 50%;
top: 0;
transform: translateX(-50%);
width: 2px;
height: 100%;
background: linear-gradient(to bottom, var(--cyan), var(--green));
opacity: .7;
}
.topo-vc-label {
position: absolute;
left: calc(50% + 6px);
top: 50%;
transform: translateY(-50%);
font-size: .58em;
color: var(--amber);
white-space: nowrap;
letter-spacing: .06em;
font-family: var(--font);
}
.topo-v2-node {
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
padding: 8px 16px;
border: 1px solid var(--border-color);
background: var(--bg3);
position: relative;
font-size: .75em;
font-family: var(--font);
min-width: 110px;
text-align: center;
transition: border-color .2s, box-shadow .2s;
}
.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-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; }
.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; }
.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;
flex-direction: column;
align-items: center;
}
.topo-v2-host-wires {
display: flex;
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; }
/* Bus rails */
.topo-bus-section {
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0;
}
.topo-bus-10g {
display: flex;
align-items: center;
position: relative;
height: 20px;
}
.topo-bus-10g-line {
flex: 1;
height: 2px;
background: var(--green);
opacity: .45;
margin: 0 4px;
}
.topo-bus-10g-label {
font-size: .56em;
color: var(--green);
white-space: nowrap;
letter-spacing: .05em;
font-family: var(--font);
opacity: .85;
padding: 0 8px;
}
.topo-bus-1g {
display: flex;
align-items: center;
position: relative;
height: 18px;
}
.topo-bus-1g-line {
flex: 1;
height: 0;
border-top: 2px dashed var(--amber);
opacity: .35;
margin: 0 4px;
}
.topo-bus-1g-label {
font-size: .56em;
color: var(--amber);
white-space: nowrap;
letter-spacing: .05em;
font-family: var(--font);
opacity: .8;
padding: 0 8px;
}
/* Hosts row */
.topo-v2-hosts {
display: flex;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
padding-top: 4px;
width: 100%;
}
/* Topology legend */
.topo-legend {
display: flex;
gap: 18px;
align-items: center;
margin-top: 14px;
padding-top: 10px;
border-top: 1px solid rgba(255,107,0,.12);
flex-wrap: wrap;
justify-content: center;
}
.topo-legend-item {
display: flex;
align-items: center;
gap: 5px;
font-size: .58em;
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-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);
}
.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;
}
.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));
}
.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);
}
/* 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;
gap: 14px;
}
.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;
padding-top: 8px;
border-top: 1px solid rgba(0,212,255,.15);
margin-top: 4px;
}
.chassis-ear-l, .chassis-ear-r {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 8px; height: 36px;
background: var(--bg3);
border: 1px solid var(--border-color);
}
.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);
}
/* 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(255,107,0,.1);
flex-wrap: wrap;
}
.chassis-legend-item {
display: flex;
align-items: center;
gap: 5px;
font-size: .58em;
color: var(--text-muted);
font-family: var(--font);
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(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); }
/* Inspector detail panel */
.inspector-panel {
width: 0;
overflow: hidden;
flex-shrink: 0;
transition: width .2s ease;
}
.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; }
/* 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;
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); }
.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;
display: inline-block;
width: 60px; height: 7px;
background: var(--bg3);
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; }
.inspector-layout { flex-direction: column; }
.inspector-panel.open { width: 100%; }
.inspector-panel-inner{ width: 100%; }
}