feat(dashboard): comprehensive config UI with help and descriptions

- Add InfoButton component with click-to-toggle popover for field help
- Add SectionDescription component for section intro paragraphs
- Add AlertRuleToggle component with grouped threshold controls
- Add detailed info and helper text for every field in all sections
- Convert Commands section to toggleable command list with descriptions
- Add dropdowns for severity_min, fire state, connection type, LLM backend
- Add region management: Add/Delete buttons with confirmation
- Group alert rules by category: Infrastructure, Power, Utilization, Health
- Remove hardcoded placeholders and Idaho-specific text
- Fix config.py DashboardConfig dataclass decorator
- Fix main.py MessageRouter initialization

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
K7ZVX 2026-05-13 04:26:17 +00:00
commit 947cce514e
6 changed files with 2497 additions and 1481 deletions

File diff suppressed because it is too large Load diff

View file

@ -466,18 +466,11 @@ class NotificationsConfig:
channels: list = field(default_factory=list) channels: list = field(default_factory=list)
rules: list = field(default_factory=list) rules: list = field(default_factory=list)
@dataclass
class DashboardConfig: class DashboardConfig:
"""Web dashboard settings.""" """Web dashboard settings."""
enabled: bool = True enabled: bool = True
# MQTT-specific fields (type=mqtt only)
host: str = "" # MQTT broker hostname
port: int = 1883 # MQTT broker port (1883 plain, 8883 TLS)
username: str = "" # MQTT username (optional)
password: str = "" # MQTT password (optional, supports )
topic_root: str = "msh/US" # Topic root to subscribe to
use_tls: bool = False # Enable TLS for MQTT connection
port: int = 8080 port: int = 8080
host: str = "0.0.0.0" host: str = "0.0.0.0"
@ -577,6 +570,8 @@ def _dict_to_dataclass(cls, data: dict):
kwargs[key] = _dict_to_dataclass(Roads511Config, value) kwargs[key] = _dict_to_dataclass(Roads511Config, value)
elif key == "firms" and isinstance(value, dict): elif key == "firms" and isinstance(value, dict):
kwargs[key] = _dict_to_dataclass(FIRMSConfig, value) kwargs[key] = _dict_to_dataclass(FIRMSConfig, value)
elif key == "dashboard" and isinstance(value, dict):
kwargs[key] = _dict_to_dataclass(DashboardConfig, value)
elif key == "notifications" and isinstance(value, dict): elif key == "notifications" and isinstance(value, dict):
notifications = _dict_to_dataclass(NotificationsConfig, value) notifications = _dict_to_dataclass(NotificationsConfig, value)
if "channels" in value and isinstance(value["channels"], list): if "channels" in value and isinstance(value["channels"], list):

View file

@ -8,8 +8,8 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-yktnPGHK.js"></script> <script type="module" crossorigin src="/assets/index-Croiw0ta.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-J-795l7V.css"> <link rel="stylesheet" crossorigin href="/assets/index-CnMjjlvK.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View file

@ -420,7 +420,7 @@ class MeshAI:
health_engine=self.health_engine, health_engine=self.health_engine,
mesh_reporter=self.mesh_reporter, mesh_reporter=self.mesh_reporter,
env_store=self.env_store, env_store=self.env_store,
notification_router=self.notification_router, # notification_router not used by MessageRouter
) )
# Responder # Responder