Files
gandalf/static/style.css
T
jared 29267c9933
Lint / Python (flake8) (push) Successful in 45s
Lint / JS (eslint) (push) Successful in 8s
Security / Python Security (bandit) (push) Successful in 41s
Test / Python Tests (pytest) (push) Successful in 52s
Lint / Notify on failure (push) Has been skipped
Lint / Deploy (push) Successful in 3s
Integrate test code improvements using web_template components
lt-alert:
- Replace custom .stale-banner with lt-alert lt-alert--warning in app.js
  and links.html; remove stale-banner CSS, reuse lt-alert margin rule

lt-progress:
- Replace custom .traffic-bar-track/.traffic-bar-fill in links.html with
  lt-progress from base.css; TX uses default (orange), RX uses --cyan,
  both flip to --red when utilisation >85% (trafficBarClass helper)
- Keep traffic layout classes (.traffic-section/.traffic-row etc.) for structure

Suppression type badges:
- Map target_type to distinct badge colors: host→badge-warning (orange),
  interface→badge-info (cyan), unifi_device→badge-purple (new alias using
  --accent-purple from base.css), all→badge-critical (red)
- Applied in both server-rendered table (Jinja2 dict lookup) and
  renderActiveRows() JS

Topology animated down-wire:
- Add data-host attribute to .topo-v2-wire-10g/.topo-v2-wire-1g elements
- updateTopology() toggles .wire-down class on the 10G drop-wire when
  host.status === 'down'
- .wire-down CSS: animated repeating-linear-gradient dashed red line
  via wire-dash-anim @keyframes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 23:37:47 -04:00

939 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); }
.badge-purple { color: var(--accent-purple); border-color: var(--accent-purple); }
/* ── Table row state colors ───────────────────────────────────────── */
.lt-table tr.row-critical td { background: rgba(255,45,85,.04); }
.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 replaced by lt-alert--warning */
/* ── 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; transition: background .3s, opacity .3s; }
.topo-v2-wire-1g { width: 0; height: 100%; border-left: 2px dashed var(--amber); opacity: .45; }
@keyframes wire-dash-anim { to { background-position: 0 -20px; } }
.topo-v2-wire-10g.wire-down {
background: repeating-linear-gradient(to bottom, var(--red) 0 6px, transparent 6px 10px) !important;
background-size: 2px 10px !important;
opacity: .9 !important;
animation: wire-dash-anim .7s linear infinite;
}
/* Bus rails */
.topo-bus-section {
width: 100%;
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 — use lt-progress from base.css */
.traffic-section { margin-top: 8px; padding-top: 8px; border-top: 1px solid rgba(255,107,0,.08); }
.traffic-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.traffic-label { font-size: .62em; color: var(--text-muted); width: 20px; text-transform: uppercase; letter-spacing: .04em; flex-shrink: 0; }
.traffic-row .lt-progress { flex: 1; height: 5px; }
.traffic-value { font-size: .7em; color: var(--text-dim); width: 68px; text-align: right; flex-shrink: 0; }
/* SFP / optical panel */
.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; }
.lt-alert { 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%; }
}