diff --git a/docker/routlin-dash/app/view_page.py b/docker/routlin-dash/app/view_page.py index e40eee6..37fedcd 100644 --- a/docker/routlin-dash/app/view_page.py +++ b/docker/routlin-dash/app/view_page.py @@ -1411,7 +1411,7 @@ def render_layout(view_id, content_html, tokens): text = e(item.get('detail', item.get('name', ''))) tip = item.get('suggestion', '') if tip: - text += f' — {e(tip)}' + text += f' - {e(tip)}' problem_bars += f'
\n' except Exception: pass diff --git a/routlin/status.py b/routlin/status.py index aa6f745..11dbd18 100644 --- a/routlin/status.py +++ b/routlin/status.py @@ -23,7 +23,7 @@ from pathlib import Path from validation import derive_interface, derive_vlan_id, is_wg # =================================================================== -# Constants (mirror core.py — no import to avoid circular dependency) +# Constants (mirror core.py - no import to avoid circular dependency) # =================================================================== PRODUCT_NAME = "routlin" @@ -44,6 +44,7 @@ RADIUS_CLIENTS_CONF = Path("/etc/freeradius/3.0/clients.conf") RADIUS_USERS_FILE = Path("/etc/freeradius/3.0/users") BLIST_TIMER_NAME = f"{PRODUCT_NAME}-dns-blocklist-update" DASHB_TIMER_NAME = f"{PRODUCT_NAME}-dashboard-queue" +STATUS_TIMER_NAME = f"{PRODUCT_NAME}-status-check" DASHB_QUEUE_FILE = SCRIPT_DIR / ".dashboard-queue" NAT_SERVICE_NAME = f"{PRODUCT_NAME}-nat" BLOCKLIST_STALE_SECS = 36 * 3600 @@ -160,6 +161,10 @@ def check_services(data): "expected_active": "inactive", "expected_enabled": "enabled"}) + units.append({"id": f"{STATUS_TIMER_NAME}.timer", + "name": f"{STATUS_TIMER_NAME}.timer", + "expected_active": "active", "expected_enabled": "enabled"}) + if DASHB_QUEUE_FILE.exists(): units.append({"id": f"{DASHB_TIMER_NAME}.timer", "name": f"{DASHB_TIMER_NAME}.timer", @@ -212,17 +217,11 @@ def check_configurations(data): vlans = data.get("vlans", []) non_wg = [v for v in vlans if not is_wg(v)] wg_vlans = [v for v in vlans if is_wg(v)] - core_mtime = CONFIG_FILE.stat().st_mtime if CONFIG_FILE.exists() else 0 - def file_ok(id_, name, path, severity="error", suggestion=""): if not path.exists(): return _problem(id_, name, severity, f"{path} does not exist.", suggestion or f"Run sudo python3 core.py --apply to create it.") - if path.stat().st_mtime < core_mtime: - return _problem(id_, name, "warning", - f"{path} is older than core.json and may be stale.", - "Run sudo python3 core.py --apply to update it.") return _ok(id_, name) # --- nftables tables --- @@ -296,12 +295,12 @@ def check_configurations(data): results.append(_problem(id_, name, "error", f"WireGuard interface {iface} does not exist.", "Run sudo python3 core.py --apply to bring up WireGuard.")) - elif state != "up": + elif state in ("up", "unknown"): # WireGuard interfaces normally report 'unknown' + results.append(_ok(id_, name)) + else: results.append(_problem(id_, name, "error", f"WireGuard interface {iface} operstate is '{state}'.", f"Try: sudo wg-quick up {iface}")) - else: - results.append(_ok(id_, name)) # --- Stale WG interfaces when no WG VLANs configured --- if not wg_vlans: @@ -391,7 +390,7 @@ def check_configurations(data): except OSError: pass # already caught above by file_ok else: - # RADIUS not enabled — warn if generated config files still exist + # RADIUS not enabled - warn if generated config files still exist if RADIUS_CLIENTS_CONF.exists(): try: if "# Generated by" in RADIUS_CLIENTS_CONF.read_text():