Add AT&T modem monitoring and device pages; replace Link Debug
Lint / Python (flake8) (pull_request) Successful in 2m6s
Lint / JS (eslint) (pull_request) Successful in 18s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 2m11s
Test / Python Tests (pytest) (pull_request) Successful in 2m20s
Lint / JS (eslint) (push) Successful in 10s
Lint / Python (flake8) (push) Successful in 1m58s
Lint / Notify on failure (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m58s
Lint / Deploy (push) Successful in 4s
Test / Python Tests (pytest) (push) Successful in 58s

Modem and WAN:
- Poll the BGW320 status pages read-only for light levels, PON and WAN
  state, with a WAN / Fiber panel, 7-day Rx trend, and alerts on WAN down,
  optical limits, and Rx drift
- Read WAN outages and Smart Power restarts from the UniFi system log,
  since outages can be over between 5 minute polls

Device pages:
- Add device search to the dashboard and a page per host, switch, router,
  AP, PDU, or modem
- Move all Link Debug data into expandable rows there; /links redirects
- Collect UDM ports and UniFi device details (firmware, load, WAN, outlets)
- Reorder the dashboard

Monitoring fixes:
- Alert when a configured host stops reporting: its series disappear
  rather than reporting down, so a dead host never raised an alert
- Alert on Prometheus outages instead of blanking the dashboard
- Treat a failed Pulse ping as unknown rather than host unreachable
- Stop new switch ports reporting lifetime bytes as one interval's rate
- Run database sessions in UTC: other server time zones shifted displayed
  ages and timed suppression expiries

Also log every avatar 404 reason, add a favicon, and correct the WAN link
labels (1G on Port 9, not 10G SFP+).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #4.
This commit is contained in:
2026-11-04 23:40:17 -05:00
co-authored by Claude Opus 5.5
parent e1716d5228
commit e32499b7f6
25 changed files with 3120 additions and 821 deletions
+8 -13
View File
@@ -6,6 +6,7 @@ import asyncio
import io
import pathlib
import sys
from urllib.parse import quote
from PIL import Image
from playwright.async_api import async_playwright
@@ -81,18 +82,12 @@ async def shots():
if hb:
await pg.locator("#hosts-grid, .host-grid, .hosts-grid").first.screenshot(path=f"{OUT}/host-cards.png")
print("host-cards ok")
await go(pg, "/links")
await pg.screenshot(path=f"{OUT}/link-debug.png")
names = await pg.evaluate("[...document.querySelectorAll('.link-host-name')].map(e=>e.textContent.trim())")
print("hosts", names)
for nm, fn in (
("large1", "link-debug-sfp-warning"),
("compute-storage-gpu-01", "link-debug-sfp"),
("Pro 24 PoE", "link-debug-switch"),
):
i = next((k for k, n in enumerate(names) if nm in n), None)
if i is not None:
await el(pg, ".link-host-panel", fn, i)
await go(pg, "/device/large1")
await pg.locator('.device-row-toggle[data-row="enp5s0"]').click()
await pg.wait_for_timeout(300)
await pg.screenshot(path=f"{OUT}/device-host.png", full_page=True)
await go(pg, "/device/" + quote("Pro 24 PoE"))
await pg.screenshot(path=f"{OUT}/device-switch.png", full_page=True)
await go(pg, "/inspector")
await pg.screenshot(path=f"{OUT}/inspector.png")
await select_port(pg, "USW-Aggregation", 4)
@@ -133,7 +128,7 @@ async def gif():
await snap(3, 600)
await pg.mouse.wheel(0, 700)
await snap(2, 600)
await go(pg, "/links", 4500)
await go(pg, "/device/large1", 4500)
await snap(3)
await pg.mouse.wheel(0, 500)
await snap(2, 500)