- Full dark terminal aesthetic (Pulse/TinkerTickets style): - #0a0a0a background, #00ff41 green, #ffb000 amber, #00ffff cyan - CRT scanline overlay, phosphor glow, ASCII corner pseudoelements - Bracket-notation badges [CRITICAL], monospace font throughout - style.css, base.html, index.html, suppressions.html all rewritten - New Link Debug page (/links, /api/links): - Per-host, per-interface cards with speed/duplex/port type/auto-neg - Traffic bars (TX cyan, RX green) with rate labels - Error/drop counters, carrier change history - SFP/DOM optical panel: vendor, temp, voltage, bias, TX/RX power dBm bars - RX-TX delta shown; color-coded warn/crit thresholds - Auto-refresh every 60s, anchor-jump to #hostname - LinkStatsCollector in monitor.py: - SSHes to each host (one connection, all ifaces batched) - Parses ethtool + ethtool -m (SFP DOM) output - Merges with Prometheus traffic/error/carrier metrics - Stores as link_stats in monitor_state table - config.json: added ssh section for ethtool collection - app.js: terminal chip style consistency (uppercase, ● bullet) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
810 lines
32 KiB
CSS
810 lines
32 KiB
CSS
/* ══════════════════════════════════════════════════════════════════════
|
||
GANDALF – Terminal aesthetic (Pulse / TinkerTickets style)
|
||
══════════════════════════════════════════════════════════════════════ */
|
||
|
||
/* ── Variables ────────────────────────────────────────────────────── */
|
||
:root {
|
||
--bg: #0a0a0a;
|
||
--bg2: #1a1a1a;
|
||
--bg3: #2a2a2a;
|
||
--bg-hover: rgba(0,255,65,.07);
|
||
|
||
--green: #00ff41;
|
||
--green-dim: rgba(0,255,65,.15);
|
||
--green-dark: #00cc33;
|
||
--green-muted: #008822;
|
||
|
||
--amber: #ffb000;
|
||
--amber-dim: rgba(255,176,0,.15);
|
||
|
||
--cyan: #00ffff;
|
||
--cyan-dim: rgba(0,255,255,.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: #008822;
|
||
|
||
--font: 'Courier New','Consolas','Monaco','Menlo',monospace;
|
||
|
||
--glow: 0 0 5px #00ff41, 0 0 10px rgba(0,255,65,.4);
|
||
--glow-xl: 0 0 8px #00ff41, 0 0 20px rgba(0,255,65,.35);
|
||
--glow-amber: 0 0 5px #ffb000, 0 0 10px rgba(255,176,0,.4);
|
||
--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);
|
||
}
|
||
|
||
/* ── 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: 13px;
|
||
line-height: 1.5;
|
||
min-height: 100vh;
|
||
position: relative;
|
||
animation: flicker .25s ease-in-out 45s infinite;
|
||
}
|
||
|
||
/* CRT scanline overlay */
|
||
body::before {
|
||
content: '';
|
||
position: fixed;
|
||
inset: 0;
|
||
background: repeating-linear-gradient(
|
||
0deg,
|
||
rgba(0,0,0,.13) 0px, rgba(0,0,0,.13) 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'; }
|
||
}
|
||
@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; }
|
||
}
|
||
|
||
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, .nav-link.active {
|
||
color: var(--green);
|
||
border-color: var(--border);
|
||
background: var(--green-dim);
|
||
text-shadow: var(--glow);
|
||
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 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 12px;
|
||
border-bottom: 1px solid var(--border);
|
||
padding-bottom: 5px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: .9em;
|
||
font-weight: bold;
|
||
color: var(--amber);
|
||
text-shadow: var(--glow-amber);
|
||
text-transform: uppercase;
|
||
letter-spacing: .1em;
|
||
}
|
||
.section-title::before { content:'╠══ '; color:var(--green); text-shadow:var(--glow); }
|
||
|
||
.section-badge {
|
||
font-size: .72em;
|
||
font-weight: bold;
|
||
color: var(--red);
|
||
border: 1px solid var(--red);
|
||
padding: 0 5px;
|
||
text-shadow: var(--glow-red);
|
||
}
|
||
.section-badge::before { content:'['; }
|
||
.section-badge::after { content:']'; }
|
||
|
||
/* ── Page header ──────────────────────────────────────────────────── */
|
||
.page-header { margin-bottom: 18px; }
|
||
.page-title {
|
||
font-size: 1.05em;
|
||
font-weight: bold;
|
||
color: var(--amber);
|
||
text-shadow: var(--glow-amber);
|
||
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; }
|
||
|
||
/* ── Status bar ───────────────────────────────────────────────────── */
|
||
.status-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
background: var(--bg2);
|
||
border: 1px solid var(--border);
|
||
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; }
|
||
|
||
.chip {
|
||
font-size: .78em;
|
||
font-weight: bold;
|
||
padding: 2px 9px;
|
||
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; }
|
||
.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;
|
||
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; }
|
||
|
||
/* ── 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;
|
||
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;
|
||
}
|
||
.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,
|
||
.dot-up, .dot-down, .dot-degraded, .dot-unknown, .dot-initial_down {
|
||
display: inline-block;
|
||
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); }
|
||
|
||
.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: 6px 14px;
|
||
border: 1px solid;
|
||
cursor: pointer;
|
||
font-family: var(--font);
|
||
font-size: .8em;
|
||
font-weight: bold;
|
||
letter-spacing: .05em;
|
||
text-transform: uppercase;
|
||
background: transparent;
|
||
transition: all .15s;
|
||
}
|
||
.btn:hover { transform: translateY(-1px); }
|
||
|
||
.btn-primary { color:var(--green); border-color:var(--green); text-shadow:var(--glow); }
|
||
.btn-primary::before { content:'> '; color:var(--amber); }
|
||
.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: 1px solid;
|
||
cursor: pointer;
|
||
background: transparent;
|
||
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: 1px solid var(--green);
|
||
box-shadow: 0 0 30px rgba(0,255,65,.18);
|
||
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);
|
||
}
|
||
|
||
.form-group input,
|
||
.form-group select {
|
||
padding: 6px 9px;
|
||
border: 1px 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); }
|
||
|
||
/* ── Duration pills ───────────────────────────────────────────────── */
|
||
.duration-pills { display:flex; gap:5px; flex-wrap:wrap; margin-bottom:5px; }
|
||
.pill {
|
||
padding: 3px 10px;
|
||
border: 1px solid var(--border);
|
||
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); }
|
||
|
||
/* ── Targets grid ─────────────────────────────────────────────────── */
|
||
.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);
|
||
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); 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;
|
||
background: var(--bg2);
|
||
animation: slide-in .15s ease;
|
||
letter-spacing: .04em;
|
||
}
|
||
.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); }
|
||
|
||
/* 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); }
|
||
|
||
/* ── 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; }
|
||
}
|