feat: inspector page, link debug enhancements, security hardening
- Add /inspector page: visual model-accurate switch chassis diagrams (USF5P, USL8A, US24PRO, USPPDUP, USMINI), clickable port blocks with color coding (green=up, amber=PoE, cyan=uplink, grey=down), detail panel with stats/PoE/LLDP, LLDP-based path debug side-by-side - Link Debug: port number badges (#N), LLDP neighbor line, PoE class/max, collapsible host/switch panels with sessionStorage persistence - monitor.py: collect LLDP neighbor map + PoE class/max/mode per switch port; PulseClient uses requests.Session() for HTTP keep-alive; add shlex.quote() around interface names (defense-in-depth) - Security: suppress buttons use data-* attrs + delegated click handler instead of inline onclick with Jinja2 variable interpolation; remove | safe filter from user-controlled fields in suppressions.html; setDuration() takes explicit el param instead of implicit event global - db.py: thread-local connection reuse with ping(reconnect=True) to avoid a new TCP handshake per query - .gitignore: add config.json (contains credentials), __pycache__ - README: full rewrite covering architecture, all 4 pages, alert logic, config reference, deployment, troubleshooting, security notes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
343
static/style.css
343
static/style.css
@@ -31,7 +31,7 @@
|
||||
|
||||
--text: #00ff41;
|
||||
--text-dim: #00cc33;
|
||||
--text-muted: #008822;
|
||||
--text-muted: #00bb33;
|
||||
|
||||
--font: 'Courier New','Consolas','Monaco','Menlo',monospace;
|
||||
|
||||
@@ -56,7 +56,7 @@ body {
|
||||
font-family: var(--font);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
@@ -788,6 +788,31 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); }
|
||||
.power-warn { background:var(--orange); }
|
||||
.power-crit { background:var(--red); box-shadow:0 0 3px var(--red); }
|
||||
|
||||
/* Collapsible link panels */
|
||||
.link-host-title {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.link-host-title:hover { background: rgba(0,255,65,.04); }
|
||||
|
||||
.panel-toggle {
|
||||
font-size: .65em;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: .04em;
|
||||
flex-shrink: 0;
|
||||
margin-left: 6px;
|
||||
padding: 0 4px;
|
||||
border: 1px solid rgba(0,255,65,.2);
|
||||
}
|
||||
.link-host-panel.collapsed > .link-ifaces-grid { display: none; }
|
||||
|
||||
/* Collapse all / Expand all bar */
|
||||
.link-collapse-bar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Link panel states */
|
||||
.link-no-data { padding:14px; color:var(--text-muted); font-size:.78em; text-align:center; }
|
||||
.link-loading { padding:20px; text-align:center; color:var(--text-muted); font-size:.8em; }
|
||||
@@ -797,6 +822,317 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); }
|
||||
.counter-zero { color:var(--green); }
|
||||
.counter-nonzero { color:var(--red); text-shadow:var(--glow-red); }
|
||||
|
||||
/* UniFi switch section divider */
|
||||
.unifi-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 24px 0 12px;
|
||||
color: var(--cyan);
|
||||
font-size: .75em;
|
||||
letter-spacing: .1em;
|
||||
text-shadow: var(--glow-cyan);
|
||||
}
|
||||
.unifi-section-header::before,
|
||||
.unifi-section-header::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, var(--cyan), transparent);
|
||||
}
|
||||
|
||||
/* Port badges (UPLINK, PoE, #N) */
|
||||
.port-badge {
|
||||
font-size: .58em;
|
||||
padding: 1px 5px;
|
||||
border: 1px solid;
|
||||
letter-spacing: .05em;
|
||||
font-weight: bold;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.port-badge-uplink { color:var(--amber); border-color:var(--amber-dim); }
|
||||
.port-badge-poe { color:var(--cyan); border-color:var(--cyan-dim); }
|
||||
.port-badge-num { color:var(--text-muted); border-color:rgba(0,255,65,.2); }
|
||||
|
||||
/* LLDP neighbor + PoE info lines on link debug cards */
|
||||
.port-lldp {
|
||||
font-size: .68em;
|
||||
color: var(--cyan);
|
||||
text-shadow: var(--glow-cyan);
|
||||
margin: -4px 0 6px;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.port-poe-info {
|
||||
font-size: .68em;
|
||||
color: var(--amber);
|
||||
margin: -4px 0 6px;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
|
||||
/* Amber value colour used in inspector */
|
||||
.val-amber { color:var(--amber); text-shadow:var(--glow-amber); }
|
||||
|
||||
/* Down port card — dim everything */
|
||||
.link-iface-card.port-down {
|
||||
opacity: .42;
|
||||
filter: saturate(.3);
|
||||
}
|
||||
|
||||
/* ── Inspector page ───────────────────────────────────────────────── */
|
||||
|
||||
/* Layout: main chassis area + collapsible right panel */
|
||||
.inspector-layout {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.inspector-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
/* Switch chassis card */
|
||||
.inspector-chassis {
|
||||
background: var(--bg2);
|
||||
border: 1px solid var(--border);
|
||||
position: relative;
|
||||
}
|
||||
.inspector-chassis::before { content:'╔'; position:absolute; top:-1px; left:-1px; color:var(--green); text-shadow:var(--glow); font-size:1rem; line-height:1; }
|
||||
.inspector-chassis::after { content:'╗'; position:absolute; top:-1px; right:-1px; color:var(--green); text-shadow:var(--glow); font-size:1rem; line-height:1; }
|
||||
|
||||
.chassis-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 16px;
|
||||
background: var(--bg3);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.chassis-name { font-weight:bold; font-size:.88em; color:var(--amber); text-shadow:var(--glow-amber); letter-spacing:.05em; }
|
||||
.chassis-name::before { content:'>> '; color:var(--green); }
|
||||
.chassis-ip { font-size:.72em; color:var(--text-muted); }
|
||||
.chassis-meta { font-size:.65em; color:var(--text-muted); margin-left:auto; }
|
||||
|
||||
.chassis-body {
|
||||
padding: 12px 16px 14px;
|
||||
}
|
||||
|
||||
/* Port rows */
|
||||
.chassis-rows { display:flex; flex-direction:column; gap:5px; margin-bottom:8px; }
|
||||
.chassis-row { display:flex; flex-wrap:wrap; gap:4px; }
|
||||
|
||||
/* SFP section below main rows */
|
||||
.chassis-sfp-section {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid rgba(0,255,255,.15);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Individual port block */
|
||||
.switch-port-block {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: .6em;
|
||||
font-weight: bold;
|
||||
border: 1px solid;
|
||||
cursor: pointer;
|
||||
transition: box-shadow .1s, border-color .1s, background .1s;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* SFP port (in rows — slightly narrower to suggest cage) */
|
||||
.switch-port-block.sfp-port {
|
||||
width: 28px;
|
||||
height: 38px;
|
||||
font-size: .55em;
|
||||
}
|
||||
|
||||
/* SFP section block (standalone cage) */
|
||||
.switch-port-block.sfp-block {
|
||||
width: 44px;
|
||||
height: 30px;
|
||||
font-size: .55em;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
/* State colours */
|
||||
.switch-port-block.down {
|
||||
background: var(--bg3);
|
||||
border-color: rgba(0,255,65,.15);
|
||||
color: rgba(0,255,65,.25);
|
||||
}
|
||||
.switch-port-block.up {
|
||||
background: rgba(0,255,65,.06);
|
||||
border-color: var(--green-muted);
|
||||
color: var(--green);
|
||||
text-shadow: 0 0 4px rgba(0,255,65,.5);
|
||||
}
|
||||
.switch-port-block.up:hover {
|
||||
background: rgba(0,255,65,.13);
|
||||
border-color: var(--green);
|
||||
box-shadow: var(--glow);
|
||||
}
|
||||
.switch-port-block.poe-active {
|
||||
background: var(--amber-dim);
|
||||
border-color: var(--amber);
|
||||
color: var(--amber);
|
||||
text-shadow: 0 0 4px rgba(255,176,0,.5);
|
||||
}
|
||||
.switch-port-block.poe-active:hover {
|
||||
box-shadow: var(--glow-amber);
|
||||
}
|
||||
.switch-port-block.uplink {
|
||||
background: var(--cyan-dim);
|
||||
border-color: var(--cyan);
|
||||
color: var(--cyan);
|
||||
text-shadow: 0 0 4px rgba(0,255,255,.5);
|
||||
}
|
||||
.switch-port-block.uplink:hover {
|
||||
box-shadow: var(--glow-cyan);
|
||||
}
|
||||
.switch-port-block.selected {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
/* Right-side detail panel */
|
||||
.inspector-panel {
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
transition: width .2s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.inspector-panel.open {
|
||||
width: 310px;
|
||||
}
|
||||
|
||||
.inspector-panel-inner {
|
||||
width: 310px;
|
||||
background: var(--bg2);
|
||||
border: 1px solid var(--border);
|
||||
padding: 14px 14px 18px;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 120px);
|
||||
}
|
||||
.inspector-panel-inner::before { content:'╔'; position:absolute; top:-1px; left:-1px; color:var(--green); text-shadow:var(--glow); font-size:1rem; line-height:1; }
|
||||
.inspector-panel-inner::after { content:'╗'; position:absolute; top:-1px; right:-1px; color:var(--green); text-shadow:var(--glow); font-size:1rem; line-height:1; }
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.panel-port-name { font-weight:bold; font-size:.92em; color:var(--amber); text-shadow:var(--glow-amber); }
|
||||
.panel-meta { font-size:.68em; color:var(--text-muted); margin-top:2px; }
|
||||
.panel-close {
|
||||
background: none;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: .8em;
|
||||
padding: 1px 7px;
|
||||
font-family: var(--font);
|
||||
flex-shrink: 0;
|
||||
transition: all .15s;
|
||||
}
|
||||
.panel-close:hover { color:var(--red); border-color:var(--red); }
|
||||
|
||||
.panel-section-title {
|
||||
font-size: .62em;
|
||||
font-weight: bold;
|
||||
color: var(--amber);
|
||||
text-shadow: var(--glow-amber);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1em;
|
||||
margin: 10px 0 5px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid rgba(0,255,65,.12);
|
||||
}
|
||||
.panel-section-title:first-of-type { margin-top: 0; }
|
||||
|
||||
.panel-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: 2px 0;
|
||||
}
|
||||
.panel-label { font-size:.68em; color:var(--text-muted); text-transform:uppercase; letter-spacing:.05em; flex-shrink:0; }
|
||||
.panel-val { font-size:.75em; font-weight:bold; color:var(--text-dim); text-align:right; word-break:break-all; }
|
||||
|
||||
/* Path debug two-column layout */
|
||||
.path-conn-type {
|
||||
font-size: .68em;
|
||||
color: var(--cyan);
|
||||
font-weight: normal;
|
||||
margin-left: 6px;
|
||||
text-shadow: none;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
.path-debug-cols {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.path-col {
|
||||
background: var(--bg3);
|
||||
border: 1px solid rgba(0,255,65,.18);
|
||||
padding: 7px 8px;
|
||||
}
|
||||
.path-col-header {
|
||||
font-size: .62em;
|
||||
font-weight: bold;
|
||||
color: var(--amber);
|
||||
margin-bottom: 5px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid rgba(0,255,65,.15);
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
.path-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
font-size: .65em;
|
||||
padding: 1px 0;
|
||||
}
|
||||
.path-row span:first-child { color:var(--text-muted); flex-shrink:0; }
|
||||
.path-row span:last-child { color:var(--text-dim); font-weight:bold; text-align:right; word-break:break-all; }
|
||||
|
||||
.path-dom {
|
||||
margin-top: 5px;
|
||||
padding-top: 5px;
|
||||
border-top: 1px solid rgba(0,255,255,.15);
|
||||
}
|
||||
.path-dom-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: .65em;
|
||||
padding: 1px 0;
|
||||
color: var(--cyan);
|
||||
}
|
||||
.path-dom-row span:first-child { color:var(--text-muted); }
|
||||
|
||||
/* ── Responsive ───────────────────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.host-grid { grid-template-columns:1fr; }
|
||||
@@ -806,4 +1142,7 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); }
|
||||
.link-ifaces-grid { grid-template-columns:1fr; }
|
||||
.sfp-grid { grid-template-columns:1fr 1fr; }
|
||||
.header-nav { display:none; }
|
||||
.inspector-layout { flex-direction:column; }
|
||||
.inspector-panel.open { width:100%; }
|
||||
.inspector-panel-inner { width:100%; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user