feat: terminal aesthetic rewrite + link debug page

- 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>
This commit is contained in:
2026-03-02 12:43:11 -05:00
parent 4356af1d84
commit fa7512a2c2
9 changed files with 1443 additions and 748 deletions

View File

@@ -42,13 +42,13 @@ function updateStatusBar(summary, lastCheck) {
const bar = document.querySelector('.status-chips');
if (!bar) return;
const chips = [];
if (summary.critical) chips.push(`<span class="chip chip-critical"> ${summary.critical} Critical</span>`);
if (summary.warning) chips.push(`<span class="chip chip-warning"> ${summary.warning} Warning</span>`);
if (!summary.critical && !summary.warning) chips.push('<span class="chip chip-ok">✔ All systems nominal</span>');
if (summary.critical) chips.push(`<span class="chip chip-critical"> ${summary.critical} CRITICAL</span>`);
if (summary.warning) chips.push(`<span class="chip chip-warning"> ${summary.warning} WARNING</span>`);
if (!summary.critical && !summary.warning) chips.push('<span class="chip chip-ok">✔ ALL SYSTEMS NOMINAL</span>');
bar.innerHTML = chips.join('');
const lc = document.getElementById('last-check');
if (lc && lastCheck) lc.textContent = `Last check: ${lastCheck}`;
if (lc && lastCheck) lc.textContent = lastCheck;
}
function updateHostGrid(hosts) {
@@ -157,15 +157,17 @@ function updateEventsTable(events) {
}).join('');
wrap.innerHTML = `
<table class="data-table" id="events-table">
<thead>
<tr>
<th>Severity</th><th>Type</th><th>Target</th><th>Detail</th>
<th>Description</th><th>First Seen</th><th>Failures</th><th>Ticket</th><th>Actions</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>`;
<div class="table-wrap">
<table class="data-table" id="events-table">
<thead>
<tr>
<th>Sev</th><th>Type</th><th>Target</th><th>Detail</th>
<th>Description</th><th>First Seen</th><th>Failures</th><th>Ticket</th><th>Actions</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</div>`;
}
// ── Suppression modal (dashboard) ────────────────────────────────────