feat: deep link diagnostics via Pulse SSH
Adds comprehensive per-port link troubleshooting triggered from the Inspector panel when a port has an LLDP-identified server counterpart. - diagnose.py: DiagnosticsRunner with 15-section SSH command (carrier, operstate, sysfs counters, ethtool, ethtool -i/-a/-g/-S/-m, ip link, ip addr, ip route, dmesg, lldpctl); parsers for all sections; health analyzer with 14 check codes (NO_CARRIER, HALF_DUPLEX, SPEED_MISMATCH, SFP_RX_CRITICAL, CARRIER_FLAPPING, CRC_ERRORS_HIGH, LLDP_MISMATCH, etc.) - monitor.py: PulseClient now tracks last_execution_id so callers can link back to the raw Pulse execution URL - app.py: POST /api/diagnose + GET /api/diagnose/<job_id> with daemon thread background execution and 10-minute in-memory job store - inspector.html: "Run Link Diagnostics" button (shown only when LLDP host is resolvable); full results panel: health banner, physical layer, SFP/DOM with power bars, NIC error counters, collapsible ethtool -S, flow control/ring buffers, driver info, LLDP 2-col validation, collapsible dmesg, switch port summary, "View in Pulse" link - style.css: all .diag-* CSS classes with terminal aesthetic Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
277
static/style.css
277
static/style.css
@@ -1133,6 +1133,283 @@ a:hover { text-decoration: underline; text-shadow: var(--glow-amber); }
|
||||
}
|
||||
.path-dom-row span:first-child { color:var(--text-muted); }
|
||||
|
||||
/* ── Link Diagnostics ─────────────────────────────────────────────── */
|
||||
.diag-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-diag {
|
||||
font-family: var(--font);
|
||||
font-size: .65em;
|
||||
color: var(--cyan);
|
||||
background: transparent;
|
||||
border: 1px solid var(--cyan);
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
letter-spacing: .04em;
|
||||
transition: background .15s, box-shadow .15s;
|
||||
animation: diag-pulse 2.5s ease-in-out infinite;
|
||||
}
|
||||
.btn-diag:hover {
|
||||
background: var(--cyan-dim);
|
||||
box-shadow: var(--glow-cyan);
|
||||
}
|
||||
|
||||
@keyframes diag-pulse {
|
||||
0%, 100% { box-shadow: none; }
|
||||
50% { box-shadow: 0 0 6px rgba(0,255,255,.4); }
|
||||
}
|
||||
|
||||
.diag-status {
|
||||
font-size: .6em;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.diag-error {
|
||||
color: var(--red);
|
||||
font-size: .65em;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.diag-results {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.diag-results-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* Health banner */
|
||||
.diag-health-banner {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 6px 0 4px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.diag-health-critical {
|
||||
background: var(--red-dim);
|
||||
color: var(--red);
|
||||
border: 1px solid var(--red);
|
||||
padding: 2px 8px;
|
||||
font-size: .62em;
|
||||
font-weight: bold;
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
|
||||
.diag-health-warning {
|
||||
background: var(--amber-dim);
|
||||
color: var(--amber);
|
||||
border: 1px solid var(--amber);
|
||||
padding: 2px 8px;
|
||||
font-size: .62em;
|
||||
font-weight: bold;
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
|
||||
.diag-health-ok {
|
||||
background: var(--green-dim);
|
||||
color: var(--green);
|
||||
border: 1px solid var(--green);
|
||||
padding: 2px 8px;
|
||||
font-size: .62em;
|
||||
font-weight: bold;
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
|
||||
/* Issue list */
|
||||
.diag-issue-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.diag-issue-row {
|
||||
font-size: .62em;
|
||||
padding: 3px 6px;
|
||||
background: var(--bg2);
|
||||
border-left: 2px solid var(--border);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.diag-code {
|
||||
font-weight: bold;
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
.diag-section {
|
||||
background: var(--bg2);
|
||||
border: 1px solid rgba(0,255,65,.12);
|
||||
}
|
||||
|
||||
.diag-section-header {
|
||||
font-size: .62em;
|
||||
font-weight: bold;
|
||||
color: var(--amber);
|
||||
padding: 4px 8px;
|
||||
letter-spacing: .04em;
|
||||
border-bottom: 1px solid rgba(0,255,65,.12);
|
||||
background: rgba(255,176,0,.04);
|
||||
}
|
||||
|
||||
/* Collapsible sections */
|
||||
.diag-collapsible .diag-section-body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.diag-collapsible.diag-open .diag-section-body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.diag-toggle {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.diag-toggle-hint {
|
||||
font-weight: normal;
|
||||
color: var(--text-muted);
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
.diag-collapsible.diag-open .diag-toggle-hint::after {
|
||||
content: '';
|
||||
}
|
||||
|
||||
/* Data tables */
|
||||
.diag-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: .62em;
|
||||
}
|
||||
|
||||
.diag-table td {
|
||||
padding: 3px 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.diag-table td:first-child {
|
||||
color: var(--text-muted);
|
||||
width: 40%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.diag-table td:last-child {
|
||||
color: var(--text-dim);
|
||||
font-weight: bold;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.diag-table tr:nth-child(even) {
|
||||
background: rgba(0,255,65,.025);
|
||||
}
|
||||
|
||||
/* Value colour classes */
|
||||
.diag-val-good { color: var(--green); }
|
||||
.diag-val-warn { color: var(--amber); }
|
||||
.diag-val-bad { color: var(--red); }
|
||||
|
||||
/* SFP power bar */
|
||||
.diag-power-bar-wrap {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 7px;
|
||||
background: var(--bg3);
|
||||
border: 1px solid var(--border);
|
||||
vertical-align: middle;
|
||||
margin-left: 6px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.diag-power-bar {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.diag-power-bar.diag-val-good { background: var(--green); }
|
||||
.diag-power-bar.diag-val-warn { background: var(--amber); }
|
||||
.diag-power-bar.diag-val-bad { background: var(--red); }
|
||||
|
||||
.diag-power-zone-warn,
|
||||
.diag-power-zone-crit {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
width: 1px;
|
||||
height: calc(100% + 4px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.diag-power-zone-warn { background: var(--amber); opacity: .7; }
|
||||
.diag-power-zone-crit { background: var(--red); opacity: .7; }
|
||||
|
||||
/* ethtool -S stat table */
|
||||
.diag-stat-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: .58em;
|
||||
}
|
||||
|
||||
.diag-stat-table td {
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.diag-stat-table td:first-child { color: var(--text-muted); }
|
||||
.diag-stat-table td:last-child { color: var(--text-dim); text-align: right; }
|
||||
|
||||
.diag-stat-nonzero-warn {
|
||||
background: var(--amber-dim);
|
||||
}
|
||||
|
||||
.diag-stat-nonzero-warn td { color: var(--amber); }
|
||||
|
||||
/* dmesg */
|
||||
.diag-dmesg-wrap {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.diag-dmesg-line {
|
||||
font-family: var(--font);
|
||||
font-size: .58em;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
padding: 1px 0;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.diag-dmesg-warn { color: var(--amber); }
|
||||
.diag-dmesg-err { color: var(--red); }
|
||||
|
||||
/* Pulse link */
|
||||
.diag-pulse-link {
|
||||
font-size: .62em;
|
||||
padding: 4px 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.diag-pulse-link a {
|
||||
color: var(--cyan);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.diag-pulse-link a:hover {
|
||||
text-shadow: var(--glow-cyan);
|
||||
}
|
||||
|
||||
/* ── Responsive ───────────────────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.host-grid { grid-template-columns:1fr; }
|
||||
|
||||
Reference in New Issue
Block a user