Commit Graph
51 Commits
Author SHA1 Message Date
jaredandClaude Opus 5.5 aa7341af06 Topology: a live page drawn from the real gear
Lint / JS (eslint) (push) Successful in 21s
Lint / Python (flake8) (push) Successful in 1m20s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m0s
Test / Python Tests (pytest) (push) Successful in 1m48s
Lint / Python (flake8) (pull_request) Successful in 1m44s
Lint / JS (eslint) (pull_request) Successful in 30s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m42s
Test / Python Tests (pytest) (pull_request) Successful in 1m16s
The dashboard's hand-drawn diagram was out of date. /topology replaces it
with the network as the switches report it, every device drawn to scale
from its faceplate or cutsheet front, and one cable per physical link
leaving the port it is plugged into.

- topology.py builds the graph: switch-to-switch cables seen from both ends
  are merged, servers get one cable per NIC, the gateway's internet port
  goes to the modem and on to the internet, unknown neighbours are stubs,
  and gear with no live cable is drawn where it was last seen.
- Ports named after a different device than the one plugged in are flagged.
- Cables show medium, speed, state and traffic; click one for its details,
  hover a device to trace its path to the internet; pan, zoom and filters.
- The monitor now keeps host_macs (MAC to host NIC) in link_stats.
- The old dashboard diagram and its JS and CSS are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 19:30:59 -05:00
jaredandClaude Opus 5.5 0921e82c54 Dashboard: flag stale alerts and tickets left open
Lint / JS (eslint) (push) Successful in 19s
Lint / Python (flake8) (push) Successful in 1m7s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 52s
Test / Python Tests (pytest) (push) Successful in 44s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Python (flake8) (pull_request) Successful in 50s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 52s
Test / Python Tests (pytest) (pull_request) Successful in 1m35s
Gandalf only comments on tickets, so a ticket can stay open long after its
alert cleared (pbs's ticket sat open for a month), and an alert can stay
active for days once nobody is looking.

- The monitor reads Tinker's open ticket list (tickets_api.php, read scope)
  every 15 minutes and saves it. A failed read keeps the previous list.
- Active alerts get a stale badge after monitor.stale_alert_days (3), and a
  "ticket closed" badge when their ticket was closed while still active.
- A Needs Follow-up panel lists open tickets whose every alert cleared at
  least monitor.stale_ticket_hours (24) ago.
- A "TO FOLLOW UP" status chip counts both. If the open ticket list is over
  2 hours old, the ticket flags are left off rather than guessed.
- Replaced an &mdash; in the alert count notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 15:57:58 -05:00
jaredandClaude Opus 5.5 619a2c913d Remove em and en dashes from the UI, code and docs
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 57s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 57s
Lint / JS (eslint) (pull_request) Successful in 10s
Lint / Python (flake8) (pull_request) Successful in 51s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m0s
Test / Python Tests (pytest) (push) Successful in 1m3s
Test / Python Tests (pytest) (pull_request) Successful in 52s
Page titles read "Rack | GANDALF", ranges use a hyphen (U26-42,
1-100 of 300), empty cells show "-", and dash-separated phrases use a
colon, comma or full stop. In the shared base.js only the on-screen strings
changed (boot line, placeholders, offline banner and toast).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 23:37:56 -05:00
jaredandClaude Opus 5.5 c262e5ecc8 Inventory links, cutsheet marks on faceplates, and single page titles
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 54s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m1s
Lint / JS (eslint) (pull_request) Successful in 19s
Test / Python Tests (pytest) (push) Successful in 2m6s
Security / Python Security (bandit) (pull_request) Successful in 1m18s
Lint / Python (flake8) (pull_request) Successful in 53s
Test / Python Tests (pytest) (pull_request) Successful in 1m1s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Device pages get an Inventory button: it opens the device's HomeBox record
once an admin pastes its URL (stored in the new device_links table; only
URLs on inventory.base_url are accepted), and searches the inventory for
the model until then. Tickets link the record too.

Faceplate ports with a documented cable get a corner dot: green matches,
red mismatch, grey documented but unconfirmed. The port's tooltip names
the cable, and a legend toggle (remembered per browser) hides them.

Page titles no longer repeat GANDALF ("Alert History - GANDALF - GANDALF").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:47:14 -05:00
jaredandClaude Opus 5.5 e32499b7f6 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>
2026-11-04 23:40:17 -05:00
jaredandClaude Sonnet 4.6 12ef20901a Fix auto-refresh ignoring 'Off' setting on page load
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
Using || 30 / || 60 as a fallback treats refreshInterval=0 (Off) as
falsy and replaces it with the default, causing auto-refresh to start
even when the user saved 'Off'. Replace with nullish coalescing (??)
so only null/undefined triggers the default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:19:44 -04:00
jaredandClaude Sonnet 4.6 13e6839cdf Show suppressed badge on host cards during global maintenance windows
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
Global suppressions (target_type='all') have an empty target_name, so
the selectattr filter never matched them, leaving no visual indicator
when a global maintenance window was active. Pre-compute has_global_sup
before the host loop and OR it into the badge condition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:12:25 -04:00
jaredandClaude Sonnet 4.6 124ca10bb0 fix: diagnostic toggle hint, link_stats schema, pagination UX, rate-limit feedback
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- inspector.html: collapsible section hint text now toggles between [expand]/[collapse] when clicked
- inspector.html: timeout and connection-loss during diagnostic poll now show a Retry button instead of a dead end
- inspector.html: 429 rate-limit response shows a clear human-readable message instead of generic error
- app.py: empty link_stats fallback now includes unifi_switches:{} for schema consistency with real data shape
- index.html: pagination overflow notice now says "export all as JSON" (opens in new tab) instead of misleadingly linking to raw API as navigation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:01:56 -04:00
jaredandClaude Sonnet 4.6 088ba789c9 security+a11y: job ownership check, aria-live chips, aria-hidden topo
security:
- Fix bare open(sentinel, 'w').close() file descriptor leak; use
  context manager instead
- Store requesting username in _diag_jobs at creation time; return 403
  from api_diagnose_poll if the polling user does not match the job owner

accessibility:
- Add aria-live="polite" aria-atomic="true" to .status-chips container
  so screen readers announce critical/warning count changes on refresh
- Add aria-controls="events-table-wrap" to critical and warning stat
  cards so assistive tech knows these buttons control the events table
- Add aria-hidden sync to topology setCollapsed() — hidden topology
  content is now removed from the accessibility tree when collapsed,
  preventing keyboard focus from entering invisible elements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 23:53:17 -04:00
jaredandClaude Sonnet 4.6 8e9a6b9b3f refactor: const/let modernisation and eliminate duplicate date-parse logic
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- Replace all var declarations in base.html, index.html scripts with
  const/let (const for bindings that are never reassigned, let otherwise)
- Add _toIso() helper to links.html script block and replace the two
  inline .replace(' UTC','Z').replace(' ','T') patterns with it
- Replace var with const in links.html _linksInterval

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 23:37:32 -04:00
jaredandClaude Sonnet 4.6 872dbf3b48 a11y: aria-pressed for all pill groups, aria-label on search inputs and buttons
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- Add role="group" + aria-label to duration-pills and sev-pills containers
- Add aria-pressed to severity filter, duration, and refresh-interval pills
- Keep aria-pressed in sync with JS (setDuration, applyRefreshPillUI, modal reset)
- Add aria-label to events-search, host-search, links-search inputs
- Add aria-label to host and UniFi device suppress buttons in templates
- Replace dynamic style color strings in links.html stat cards with TDS
  utility classes (lt-text-red/green/amber) via downCls/errCls variables

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 23:34:16 -04:00
jaredandClaude Sonnet 4.6 bc71cc829b CSS: replace remaining inline color/size styles with TDS utilities
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- Stat card icons and values: style="color:var(--red)" etc replaced with
  lt-text-red, lt-text-amber, lt-text-cyan, lt-text-green (defined in
  base.css with both color and glow-shadow)
- Host search input: style="width:180px" extracted to .lt-search-input--sm
- base.html: suppress modal form groups use lt-form-group--last for last
  item (already committed); lt-divider--compact applied to settings divider

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 23:17:22 -04:00
jaredandClaude Sonnet 4.6 c3f0fcbefb CSS: remove remaining fixable inline styles across templates
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- Suppress modal form groups: strip margin-bottom:12px (lt-form-group
  already has margin-bottom in TDS); use lt-form-group--last for the
  final group where zero margin is needed
- Keyboard shortcuts table: remove width:100% (lt-table is already full-
  width in base.css)
- Settings divider: replace style=margin override with .lt-divider--compact
- Topology bus section: move max-width:860px into .topo-bus-section rule

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 23:15:15 -04:00
jaredandClaude Sonnet 4.6 d8a4474814 CSS: convert all topology inline styles to modifier classes
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
Replace style= attributes on topology wire/node elements with semantic
modifier classes:
- topo-vc-wire--wan, --10g, --mgmt (wire colour semantics in CSS)
- topo-v2-host--bus (bus-section node size constraint)
- topo-legend-item--offrack already done in prior commit

Zero inline styles remain in the topology section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 23:13:24 -04:00
jaredandClaude Sonnet 4.6 9e0f8555cc Feature: show suppression status on active alert rows
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
Active events now carry an is_suppressed boolean (added in api_status()
and the index() route via check_suppressed() against the pre-loaded
suppression list). The events table renders a muted '🔕 sup' badge next
to the severity and dims the entire row (.row-suppressed) so operators
can immediately see which firing alerts are silenced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 23:11:15 -04:00
jaredandClaude Sonnet 4.6 f25a377968 Cleanup: strip redundant inline styles, add CSS classes
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- Remove style="margin-top:4px" from .g-page-sub in all three secondary
  pages (the value is already defined in .g-page-sub rule in style.css)
- suppressions.html: replace inline style="padding:12px 14px" with TDS
  lt-section-body class
- index.html topology legend: replace inline dashed-border style with
  .topo-legend-item--offrack modifier class in style.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 23:09:25 -04:00
jaredandClaude Sonnet 4.6 7abb76afaa Redesign: alerts above fold, fix nav, globalise suppress modal
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- Dashboard: reorder sections so Active Alerts appear immediately below
  stat cards rather than after 200+ lines of topology — primary content
  is now above the fold on all screen sizes
- Network Hosts section gains a collapse toggle (persisted to localStorage)
  so the topology diagram can be hidden when not needed
- Admin nav corrected: admin now gets a direct Suppressions link;
  non-admin no longer sees the page at all (it was always admin-only)
- Suppress modal moved from index.html into base.html so [S] keyboard
  shortcut works on every page, not just the dashboard; form listeners
  wired in app.js accordingly
- Settings modal KV grid: replaced lt-kv-row/lt-kv-label/lt-kv-value
  (light-mode only) with lt-kv-key/lt-kv-val (dark-mode defined)
- style.css: add lt-btn-secondary dark-mode definition, lt-cmd-hint-btn
  class for ⌘K button, topology collapse styles; remove dead .g-page-header,
  .g-page-title, .empty-state classes; strip redundant inline styles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 23:07:53 -04:00
jaredandClaude Sonnet 4.6 332f041e05 Dynamic resolved count, host search filter, lt-divider for UniFi section
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- db.py: add resolved_24h to get_status_summary() so each /api/status
  poll carries the fresh 24h resolved count
- app.js: wire stat-resolved-val to update from summary.resolved_24h
  so the Resolved 24h card stays accurate after auto-refresh
- index.html: add lt-toolbar/lt-search above host grid for quick
  client-side host filtering by name
- links.html: replace custom unifi-section-header div with lt-divider
- style.css: remove unused .unifi-section-header rules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 18:36:57 -04:00
jaredandClaude Sonnet 4.6 ddc258a453 TDS polish: lt-frame tables, links search toolbar, dead CSS cleanup
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- index.html: wrap UniFi devices table in lt-frame with section header
- links.html: add static lt-toolbar with lt-search filter and collapse
  controls above the dynamic container; remove collapse bar from
  renderLinks() since it's now static; add applyLinksSearch() to
  filter host/switch panels by name as user types
- suppressions.html: wrap Available Targets section in lt-frame
- style.css: remove unused .link-summary-panel and related rules
  (replaced by lt-stats-grid in previous commit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 17:39:11 -04:00
jaredandClaude Sonnet 4.6 9d15d4b802 Add stat cards, lt-frame alert queue, and timeline for resolved alerts
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- Four lt-stat-card widgets (Critical, Warning, Hosts, Resolved 24h)
  below the status bar; Critical card pulses red when count > 0
- Clicking Critical or Warning card filters the events table by severity
- Events table wrapped in lt-frame with ASCII corner ornaments and
  lt-section-header; filter bar moved to lt-toolbar with lt-search icon
- Recently Resolved table replaced with lt-timeline component
- updateStatusBar() and updateHostGrid() keep stat card values live

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 22:19:50 -04:00
jaredandClaude Sonnet 4.6 6f9787b74a Add notification bell, settings modal, and context-sensitive footer
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- Notification bell in header polls /api/status and shows active alerts
  with severity-colored dots; badge counts unread items via localStorage
- Settings modal ([ * ] CFG) controls auto-refresh interval (15s/30s/1m/5m/off)
  persisted to localStorage and wired into lt.autoRefresh on all pages
- Context-sensitive footer hints: Dashboard shows REFRESH + SUPPRESS,
  Link Debug shows REFRESH, all pages show CFG + HELP
- Added S key (quick suppress) and * key (settings) shortcuts
- ⌘K affordance button added to header-right
- R key now uses lt.autoRefresh.now() so it works on any page
- refreshAll() pushes fresh events to notification bell on each poll

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 21:33:02 -04:00
jaredandClaude Sonnet 4.6 032d0db81e Add LDAP avatar photos, UX polish, and TDS component upgrades
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- Add /api/avatar endpoint querying lldap for user jpegPhoto; disk cache
  with sentinel pattern avoids repeat LDAP hits for users without photos
- Add ldap3 dependency and ldap config block to config.json
- Wire lt-avatar img overlay in base.html with capture-phase error
  fallback (lt-avatar-img-err) to reveal initials when image is absent
- Fix lt-avatar CSS shim: position:relative + absolute inset on img
  (local base.css was missing these; added to style.css)
- Replace all empty-state paragraphs with proper lt-empty-state markup
  (icon + title + body) across index, suppressions, inspector, app.js
- Add lt-spinner--cyan next to refresh button; shows during refreshAll()
- Replace inspector panel-section-title with lt-divider throughout
- Add data-tooltip attributes to SFP DOM metrics, TX/RX/Carrier/Duplex/
  Auto-neg/Error labels in links.html and inspector panel
- Add tooltips to events table column headers (Sev, First Seen, Failures)
- Fix links.html host panel timestamp (was reading sample.updated which
  is always undefined; now uses data.updated)
- Fix UniFi status text casing (Online→ONLINE to match server render)
- Remove dead topo-status-* class manipulation from updateTopology()
- Always render alert-count-badge; toggle display:none when count is 0
- Fix double UniFi get_devices() call in monitor.py run loop
- Fix chip-critical animation (was using green pulse-glow; now red)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 21:09:56 -04:00
jaredandClaude Sonnet 4.6 5fdb77d664 Integrate test code improvements using web_template components
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
lt-alert:
- Replace custom .stale-banner with lt-alert lt-alert--warning in app.js
  and links.html; remove stale-banner CSS, reuse lt-alert margin rule

lt-progress:
- Replace custom .traffic-bar-track/.traffic-bar-fill in links.html with
  lt-progress from base.css; TX uses default (orange), RX uses --cyan,
  both flip to --red when utilisation >85% (trafficBarClass helper)
- Keep traffic layout classes (.traffic-section/.traffic-row etc.) for structure

Suppression type badges:
- Map target_type to distinct badge colors: host→badge-warning (orange),
  interface→badge-info (cyan), unifi_device→badge-purple (new alias using
  --accent-purple from base.css), all→badge-critical (red)
- Applied in both server-rendered table (Jinja2 dict lookup) and
  renderActiveRows() JS

Topology animated down-wire:
- Add data-host attribute to .topo-v2-wire-10g/.topo-v2-wire-1g elements
- updateTopology() toggles .wire-down class on the 10G drop-wire when
  host.status === 'down'
- .wire-down CSS: animated repeating-linear-gradient dashed red line
  via wire-dash-anim @keyframes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 23:37:47 -04:00
jaredandClaude Sonnet 4.6 d05deb6ccd Remove all inline event handlers; replace with data-action delegation
Lint / Python (flake8) (push) Skipped
Lint / JS (eslint) (push) Skipped
- inspector.html: onclick on port blocks, close button, run-diagnostic button,
  and diag-toggle sections all converted to data-action attributes; single
  delegated click listener handles all cases + Escape key closes panel
- links.html: onclick on panel title headers, Collapse All, Expand All
  converted to data-action with delegated listener
- suppressions.html: onsubmit/onchange wired via addEventListener at init
- index.html: onsubmit/onchange on suppress modal form wired at init

No behavioural changes — pure event-handling refactor for TDS compliance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 17:53:48 -04:00
jaredandClaude Sonnet 4.6 3a9cc472e1 Audit quick wins: null guard, API error toasts, aria-labels on suppress buttons
- app.js: guard events array with || [] before .filter() to prevent crash on null
- app.js: show warning toast when /api/network or /api/status fail (was silent)
- app.js: add aria-label to all dynamically-generated suppress buttons
- index.html: add aria-label to server-rendered suppress buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 17:50:00 -04:00
jaredandClaude Sonnet 4.6 1767c12f1b Fix field name mismatches, add events filter, in-place suppression refresh
- links.html: fix all field name bugs (auto_negotiation→autoneg, full_duplex,
  tx/rx_errors/drops_per_sec→_rate, tx/rx_bytes_per_sec→_rate, poe_total_w/poe_max_w
  computed from ports, renderUnifiSwitches uses top-level updated timestamp)
- suppressions.html: in-place DOM refresh after create/remove (no page reload),
  datalist autocomplete for target names, form reset after submit
- inspector.html: ESC key closes detail panel via lt.keys.on
- index.html: events filter bar with search input + severity pills (All/Critical/Warning),
  MutationObserver re-applies filter after dynamic updates
- style.css: g-section-actions, events-filter-bar, sev-pills layout
- app.js/db.py/monitor.py: carry forward prior session fixes (Promise.allSettled,
  daemon_ok, stale connection handling, double Prometheus call, self.cfg fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 23:35:02 -04:00
jaredandClaude Sonnet 4.6 1e9ce08dca Integrate TDS v1.2 lt.* APIs throughout app
- app.js: replace raw fetch/escHtml/fmtRelTime with lt.api, lt.escHtml, lt.time.ago; modal open/close via lt.modal; add _toIso() for timestamp normalisation
- index.html: data-action="refresh", data-duration pills, lt.autoRefresh.start, remove local fmtRelTime
- suppressions.html: lt.api.post/delete, data-dur pill delegation
- base.html: user avatar with initials, admin badge, lt.keys.on('r') replaces manual keydown handler
- base.css: add dot-*, chip, row-state aliases so apps can use unprefixed class names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 23:46:44 -04:00
jaredandClaude Sonnet 4.6 51c18fee5c Align base.html and modal with tinker_tickets reference implementation
- Add lt-glitch + data-text to brand title (signature glitch effect)
- Add mobile nav drawer (lt-nav-drawer) and hamburger button (lt-menu-btn)
- Add VT323 font, theme toggle button (lt-theme-btn), footer with hints
- Add command palette overlay + lt.cmdPalette.init() with nav commands
- Add keyboard shortcuts help modal and skip link
- Move base.js to <head> so lt.* is available for inline scripts
- Fix suppress modal: lt-modal-backdrop → lt-modal-overlay (base.css class)
- Fix modal open/close: use .is-open / aria-hidden instead of inline style

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 21:42:04 -04:00
jaredandClaude Sonnet 4.6 b61501d81d Restructure app to use LotusGuild Terminal Design System v1.2
Replace custom phosphor-green terminal aesthetic with the lt-* component
system from base.css/base.js. All templates now inherit the LotusGuild
multi-accent Anduril palette via variable aliases in style.css, and use
lt-header, lt-nav, lt-card, lt-table, lt-btn, lt-modal, lt-badge etc.
Custom components (topology, inspector chassis, link debug, SFP panels)
are preserved with color values updated to base.css palette variables.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 21:01:20 -04:00
jaredandClaude Sonnet 4.6 44d4a7d1eb Add pagination to event queries, input validation, daily event purge
- get_active_events() now takes limit/offset (default 200) to cap unbounded queries
- count_active_events() added to return total for pagination display
- /api/events supports ?limit=, ?offset=, ?status= query params (max 1000)
- /api/status includes total_active count alongside paginated events list
- index() route passes total_active to template for server-side truncation notice
- Show "Showing X of Y" notice in dashboard when events are truncated
- Suppression POST validates: reason ≤500 chars, target_name/detail ≤255 chars
- _purge_old_jobs_loop runs purge_old_resolved_events(90d) once per day

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 20:32:32 -04:00
jaredandClaude Sonnet 4.6 892d1cfe2d Fix topology chain order and inspector SFP port width
Topology:
- Correct series layout: UDM-Pro → USW-Agg → Pro 24 PoE (not a fork)
- Remove CSS fork divs, replace with straight vertical connectors
- Labels: WAN · 10G SFP+ (UDM→Agg), 10G trunk (Agg→PoE)
- Remove ISL from legend (no parallel switch pair)

Inspector:
- Fix USW-Agg port blocks appearing narrower than other switches
- SFP ports in rows now use same width (34px) as copper ports;
  all-SFP switches like USL8A no longer look undersized

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:42:38 -04:00
jaredandClaude Sonnet 4.6 834acfd393 Fix topology diagram: replace SVG fork with CSS, fix line alignment
- Remove SVG fork with preserveAspectRatio="none" (caused line width
  distortion and stretched 10G DAC label like a tube TV)
- Replace with pure CSS .topo-fork: stem + horizontal bar + left/right
  drops, all absolutely positioned at consistent 2px width
- Use .topo-sw-row with two 50% halves so switch centres land at
  exactly 25% and 75% — matching fork drop positions mathematically
- ISL rendered via ::before/::after on .topo-sw-row (switch boxes
  with solid bg cover the line at their edges, leaving only the gap)
- Add .topo-sw-drops: two vertical stubs from switch centres to bus rails
- All lines are now exactly 2px, no distortion, no misalignment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:35:02 -04:00
jaredandClaude Sonnet 4.6 148fc57cc2 Redesign topology diagram with dual-homed bus layout and improve inspector chassis
- Replace flat topology with tiered bus-bar layout: Internet → UDM-Pro → SVG fork → USW-Agg + Pro 24 PoE → dual-homed servers
- Show 10G VLAN90 (Ceph) bus from USW-Agg and 1G DHCP management bus from Pro 24 PoE per host
- Add per-host drop wires (solid 10G + dashed 1G) with correct rack positions
- Mark large1 as off-rack (dashed border), ZimaBoards as off-rack mon-01/mon-02
- Add topology legend, inter-switch 10G ISL indicator
- Add recently resolved events section (last 24h) to dashboard
- Add last_seen column and relative timestamps to events table
- Add stale data banner when monitoring data >15 min old
- Improve inspector chassis with port speed labels, LLDP neighbor info, mounting ears, chassis legend
- Add duplex/speed mismatch warnings and carrier changes to path debug panel
- Bump updateTopology() to handle both topo-v2-status-* and topo-status-* classes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:22:19 -04:00
jaredandClaude Sonnet 4.6 35d785db64 fix: topology — reflect VLAN90 Ceph network and DHCP management separation
10G SFP+ ports on USW-Agg are VLAN90 (10.10.90.x/24, static IPs, Ceph storage).
1G ports on Pro 24 PoE are DHCP management. Update topology to show this:
- USW-Agg sublabel shows VLAN90 · 10.10.90.x (cyan)
- Pro 24 PoE sublabel shows DHCP mgmt (cyan)
- Host sublabels changed from "10G+1G" to "VLAN90" for the 10G Agg connection
- 1G management band label updated to "← 1G DHCP mgmt (Pro 24 PoE) →"
- Add .topo-vlan-tag CSS for cyan VLAN annotation on switch nodes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:10:17 -04:00
jaredandClaude Sonnet 4.6 f3753092e0 fix: topology — all servers dual-homed 10G+1G, show mgmt band
All rack servers (and large1 on table) have both a 10G link to USW-Agg
and a 1G management link to Pro 24 PoE. Update topology:
- Move all 6 hosts into single row (including large1)
- Update sublabels to "10G+1G" for all nodes
- large1 dashed-border (off-rack) with "table · 10G+1G"
- Add dashed amber "1G mgmt (PoE)" horizontal band above hosts
  to represent the PoE switch management connections
- 10G primary fan-out lines still drop from Agg switch above
- large1 primary line rendered as dashed green (off-rack run)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:08:48 -04:00
jaredandClaude Sonnet 4.6 55989f4c50 feat: redesign network topology diagram with accurate rack layout
Replace linear Internet→UDM→Agg→PoE→all-hosts chain with accurate topology:
- USW-Aggregation and Pro 24 PoE switch shown side-by-side with horizontal
  10G SFP+ link between them (not in series)
- 5 compute/storage/monitor nodes fanned out under Agg Switch with 10G labels
  and rack unit positions (RU4–12, RU14–17) as sublabels
- large1 shown separately under PoE switch, dashed border = off-rack (table)
- Add device specs as subtitles on all nodes (Dream Machine Pro · RU24, etc.)
- Shorter display names: csg-01 / cs-01 instead of full hostnames
- Live status badges still updated by JS via data-host attributes
- New CSS: .topo-node-sub, .topo-switch-tier, .topo-h-link, .topo-host-tier,
  .topo-host-table (dashed), .topo-badge-unknown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:06:03 -04:00
jaredandClaude Sonnet 4.6 3840d2ccb7 feat: link health summary, recently resolved panel, event duration
- dashboard: pass recent_resolved (last 24h, limit 10) to index template;
  render "Recently Resolved" section showing type, target, resolved time,
  and calculated duration (first_seen → resolved_at)
- dashboard: event-age spans now also update via setInterval; duration
  shown for resolved events (e.g. "2h 15m")
- links page: link health summary panel shows server iface count,
  error/flap counts, switch port up/down, PoE total draw/capacity bar;
  only shows problematic stats if non-zero; shows "All OK ✔" when clean
- style.css: new classes for summary panel, resolved row/badge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 21:48:40 -04:00
jaredandClaude Sonnet 4.6 df7b7e36e5 feat: UI improvements — event ages, error badges, PoE bars, mismatch detection
- events table: add Last Seen column; show relative times ("3h ago") with
  absolute timestamp on hover; update updateEventsTable() in app.js to match
- links.html: add error/drop/flap alert badges to interface and port card headers
- links.html: PoE power bar (draw/max ratio with colour-coded fill) and poe_mode
- links.html: stale data warning banner when link_stats are >2 minutes old
- links.html: improved error handler shows HTTP status instead of generic message
- links.html: fix collapse state persisted to localStorage (was sessionStorage,
  lost on browser restart); fix collapseAll/expandAll to also persist state
- inspector.html: duplex mismatch and speed mismatch warnings in path debug panel
- inspector.html: carrier changes added to server column of path debug
- style.css: new classes — .link-alert-badge, .poe-bar-*, .path-mismatch-alert,
  .error-state; fix .stale-banner to use CSS variables

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 21:46:11 -04:00
jaredandClaude Sonnet 4.6 a7c3f97252 De-hardcode ticket URL and cluster name; improve diagnostic polling UX
app.py:
- Context processor injects config.ticket_api.web_url into all templates
  (falls back to 'http://t.lotusguild.org/ticket/' if not set in config)

templates/base.html:
- Inject GANDALF_CONFIG JS global with ticket_web_url before app.js loads

static/app.js:
- Use GANDALF_CONFIG.ticket_web_url instead of hardcoded domain

templates/index.html:
- Use {{ config.ticket_api.web_url }} Jinja var instead of hardcoded domain

monitor.py:
- CLUSTER_NAME constant kept as default; NetworkMonitor now reads cluster_name
  from config monitor.cluster_name, falling back to the constant
- All CLUSTER_NAME references inside class methods replaced with self.cluster_name

templates/inspector.html:
- pollDiagnostic() .catch() now clears interval and shows error message instead
  of silently ignoring network failures during active polling

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 14:31:57 -04:00
jaredandClaude Sonnet 4.6 c51c24bbb5 Fix setDur implicit event, title XSS, hardcoded pulse URL, suppress error toast
- suppressions.html: setDur() now takes explicit element param instead of relying
  on implicit global event.target (which fails outside direct click handlers)
- suppressions.html: removeSuppression() now shows error toast on failed DELETE
- templates/index.html: escape description in title attribute with |e filter
  to prevent attribute breakout on quotes in description text
- diagnose.py: derive Pulse execution URL from pulse_client.url instead of
  hardcoding http://pulse.lotusguild.org

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 14:36:55 -04:00
jaredandClaude Sonnet 4.6 468dbda7a1 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>
2026-03-03 15:39:48 -05:00
jaredandClaude Sonnet 4.6 c8e183fc4f 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>
2026-03-02 12:43:11 -05:00
jaredandClaude Sonnet 4.6 04a5182259 Complete rewrite: full-featured network monitoring dashboard
- Two-service architecture: Flask web app (gandalf.service) + background
  polling daemon (gandalf-monitor.service)
- Monitor polls Prometheus node_network_up for physical NIC states on all
  6 hypervisors (added storage-01 at 10.10.10.11:9100)
- UniFi API monitoring for switches, APs, and gateway device status
- Ping reachability for hosts without node_exporter (pbs only now)
- Smart baseline: interfaces first seen as down are never alerted on;
  only UP→DOWN regressions trigger tickets
- Cluster-wide P1 ticket when 3+ hosts have genuine simultaneous
  interface regressions (guards against false positives on startup)
- Tinker Tickets integration with 24-hour hash-based deduplication
- Alert suppression: manual toggle or timed windows (30m/1h/4h/8h)
- Authelia SSO via forward-auth headers, admin group required
- Network topology: Internet → UDM-Pro → Agg Switch (10G DAC) →
  PoE Switch (10G DAC) → Hosts
- MariaDB schema, suppression management UI, host/interface cards

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 23:03:18 -05:00
jared 517843886b interface update 2025-02-08 00:32:25 -05:00
jared 705be36c3f updated index html 2025-02-07 23:51:13 -05:00
jared 9188401374 update html 2025-02-07 23:38:49 -05:00
jared 21bd5d8dbf added diag content 2025-02-07 21:33:55 -05:00
jared a35f0527e3 Added interface status 2025-02-07 21:28:54 -05:00
jared d671916100 Update file structure for Flask 2025-02-07 21:22:43 -05:00
jared 21dfad35bf made everything static 2025-01-04 01:07:18 -05:00