Fix field name mismatches, add events filter, in-place suppression refresh
Lint / Python (flake8) (push) Failing after 50s
Lint / JS (eslint) (push) Successful in 7s
Test / Python Tests (pytest) (push) Successful in 51s
Lint / Notify on failure (push) Successful in 2s
Lint / Deploy (push) Has been skipped
Security / Python Security (bandit) (push) Failing after 59s
Lint / Python (flake8) (push) Failing after 50s
Lint / JS (eslint) (push) Successful in 7s
Test / Python Tests (pytest) (push) Successful in 51s
Lint / Notify on failure (push) Successful in 2s
Lint / Deploy (push) Has been skipped
Security / Python Security (bandit) (push) Failing after 59s
- 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>
This commit is contained in:
@@ -4,6 +4,7 @@ Flask web application serving the monitoring dashboard and suppression
|
||||
management UI. Authentication via Authelia forward-auth headers.
|
||||
All monitoring and alerting is handled by the separate monitor.py daemon.
|
||||
"""
|
||||
import hashlib
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
@@ -11,6 +12,7 @@ import re
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from functools import wraps
|
||||
|
||||
from flask import Flask, jsonify, render_template, request
|
||||
@@ -31,9 +33,10 @@ _AVATAR_COLORS = ['lt-avatar--orange', 'lt-avatar--green', 'lt-avatar--purple',
|
||||
|
||||
@app.template_filter('avatar_color')
|
||||
def avatar_color_filter(name: str) -> str:
|
||||
return _AVATAR_COLORS[abs(hash(name)) % len(_AVATAR_COLORS)]
|
||||
return _AVATAR_COLORS[int(hashlib.md5(name.encode()).hexdigest(), 16) % len(_AVATAR_COLORS)]
|
||||
|
||||
_cfg = None
|
||||
_cfg_lock = threading.Lock()
|
||||
|
||||
|
||||
@app.context_processor
|
||||
@@ -54,7 +57,6 @@ _diag_jobs: dict = {}
|
||||
_diag_lock = threading.Lock()
|
||||
|
||||
|
||||
_last_event_purge = [0.0] # mutable container so the thread can update it
|
||||
|
||||
|
||||
def _purge_old_jobs_loop():
|
||||
@@ -67,21 +69,12 @@ def _purge_old_jobs_loop():
|
||||
stale = [jid for jid, j in _diag_jobs.items() if j.get('created_at', 0) < cutoff]
|
||||
for jid in stale:
|
||||
del _diag_jobs[jid]
|
||||
for jid, j in _diag_jobs.items():
|
||||
for jid, j in list(_diag_jobs.items()):
|
||||
if j['status'] == 'running' and j.get('created_at', 0) < stuck_cutoff:
|
||||
j['status'] = 'done'
|
||||
j['result'] = {'status': 'error', 'error': 'Diagnostic timed out (thread crash)'}
|
||||
logger.error(f'Diagnostic job {jid} appeared stuck; marked as errored')
|
||||
|
||||
# Purge old resolved events once per day
|
||||
now = time.time()
|
||||
if now - _last_event_purge[0] > 86400:
|
||||
try:
|
||||
db.purge_old_resolved_events(days=90)
|
||||
except Exception as e:
|
||||
logger.error(f'Daily event purge failed: {e}')
|
||||
_last_event_purge[0] = now
|
||||
|
||||
|
||||
_purge_thread = threading.Thread(target=_purge_old_jobs_loop, daemon=True)
|
||||
_purge_thread.start()
|
||||
@@ -90,11 +83,24 @@ _purge_thread.start()
|
||||
def _config() -> dict:
|
||||
global _cfg
|
||||
if _cfg is None:
|
||||
with open('config.json') as f:
|
||||
_cfg = json.load(f)
|
||||
with _cfg_lock:
|
||||
if _cfg is None:
|
||||
with open('config.json') as f:
|
||||
_cfg = json.load(f)
|
||||
return _cfg
|
||||
|
||||
|
||||
def _daemon_ok(last_check: str) -> bool:
|
||||
"""Return True if monitor last checked within 20 minutes."""
|
||||
if not last_check or last_check == 'Never':
|
||||
return False
|
||||
try:
|
||||
ts = datetime.strptime(last_check, '%Y-%m-%d %H:%M:%S UTC').replace(tzinfo=timezone.utc)
|
||||
return (datetime.now(timezone.utc) - ts).total_seconds() < 1200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Auth helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -206,11 +212,13 @@ def suppressions_page():
|
||||
@require_auth
|
||||
def api_status():
|
||||
active = db.get_active_events(limit=_PAGE_LIMIT)
|
||||
last_check = db.get_state('last_check', 'Never')
|
||||
return jsonify({
|
||||
'summary': db.get_status_summary(),
|
||||
'last_check': db.get_state('last_check', 'Never'),
|
||||
'last_check': last_check,
|
||||
'events': active,
|
||||
'total_active': db.count_active_events(),
|
||||
'daemon_ok': _daemon_ok(last_check),
|
||||
})
|
||||
|
||||
|
||||
@@ -453,7 +461,6 @@ def health():
|
||||
try:
|
||||
last_check = db.get_state('last_check', '')
|
||||
if last_check:
|
||||
from datetime import datetime, timezone
|
||||
ts = datetime.strptime(last_check, '%Y-%m-%d %H:%M:%S UTC').replace(tzinfo=timezone.utc)
|
||||
age_s = (datetime.now(timezone.utc) - ts).total_seconds()
|
||||
if age_s > 1200:
|
||||
|
||||
Reference in New Issue
Block a user