From 947cce514e9e197cb25d94884ce1ccb5b30c8a50 Mon Sep 17 00:00:00 2001 From: K7ZVX Date: Wed, 13 May 2026 04:26:17 +0000 Subject: [PATCH] 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 --- dashboard-frontend/src/pages/Config.tsx | 3769 +++++++++++------ meshai/config.py | 11 +- ...{index-J-795l7V.css => index-CnMjjlvK.css} | 2 +- .../{index-yktnPGHK.js => index-Croiw0ta.js} | 190 +- meshai/dashboard/static/index.html | 4 +- meshai/main.py | 2 +- 6 files changed, 2497 insertions(+), 1481 deletions(-) rename meshai/dashboard/static/assets/{index-J-795l7V.css => index-CnMjjlvK.css} (51%) rename meshai/dashboard/static/assets/{index-yktnPGHK.js => index-Croiw0ta.js} (52%) diff --git a/dashboard-frontend/src/pages/Config.tsx b/dashboard-frontend/src/pages/Config.tsx index 7c58905..9901c29 100644 --- a/dashboard-frontend/src/pages/Config.tsx +++ b/dashboard-frontend/src/pages/Config.tsx @@ -1,1374 +1,2395 @@ -import { useState, useEffect, useCallback } from 'react' -import { - Settings, Bot, Wifi, MessageSquare, Database, Brain, Eye, - Terminal, Cpu, Cloud, Radio, BookOpen, Layers, Activity, - Thermometer, LayoutDashboard, Save, RotateCcw, RefreshCw, - Plus, Trash2, ChevronDown, ChevronRight, AlertTriangle, - Check, X, Eye as EyeIcon, EyeOff -} from 'lucide-react' - -// Types for config sections -interface BotConfig { - name: string - owner: string - respond_to_dms: boolean - filter_bbs_protocols: boolean -} - -interface ConnectionConfig { - type: string - serial_port: string - tcp_host: string - tcp_port: number -} - -interface ResponseConfig { - delay_min: number - delay_max: number - max_length: number - max_messages: number -} - -interface HistoryConfig { - database: string - max_messages_per_user: number - conversation_timeout: number - auto_cleanup: boolean - cleanup_interval_hours: number - max_age_days: number -} - -interface MemoryConfig { - enabled: boolean - window_size: number - summarize_threshold: number -} - -interface ContextConfig { - enabled: boolean - observe_channels: number[] - ignore_nodes: string[] - max_age: number - max_context_items: number -} - -interface CommandsConfig { - enabled: boolean - prefix: string - disabled_commands: string[] - custom_commands: Record -} - -interface LLMConfig { - backend: string - api_key: string - base_url: string - model: string - timeout: number - max_response_tokens: number - system_prompt: string - use_system_prompt: boolean - web_search: boolean - google_grounding: boolean -} - -interface WeatherConfig { - primary: string - fallback: string - default_location: string - openmeteo: { url: string } - wttr: { url: string } -} - -interface MeshMonitorConfig { - enabled: boolean - url: string - inject_into_prompt: boolean - refresh_interval: number - polite_mode: boolean -} - -interface KnowledgeConfig { - enabled: boolean - backend: string - qdrant_host: string - qdrant_port: number - qdrant_collection: string - tei_host: string - tei_port: number - sparse_host: string - sparse_port: number - use_sparse: boolean - db_path: string - top_k: number -} - -interface MeshSourceConfig { - name: string - type: string - url: string - api_token: string - refresh_interval: number - polite_mode: boolean - enabled: boolean - // MQTT-specific fields - host?: string - port?: number - username?: string - password?: string - topic_root?: string - use_tls?: boolean -} - -interface RegionAnchor { - name: string - lat: number - lon: number - local_name: string - description: string - aliases: string[] - cities: string[] -} - -interface AlertRulesConfig { - infra_offline: boolean - infra_recovery: boolean - new_router: boolean - battery_trend_declining: boolean - battery_warning: boolean - battery_critical: boolean - battery_emergency: boolean - battery_warning_threshold: number - battery_critical_threshold: number - battery_emergency_threshold: number - power_source_change: boolean - solar_not_charging: boolean - sustained_high_util: boolean - high_util_threshold: number - high_util_hours: number - packet_flood: boolean - packet_flood_threshold: number - infra_single_gateway: boolean - feeder_offline: boolean - region_total_blackout: boolean - mesh_score_alert: boolean - mesh_score_threshold: number - region_score_alert: boolean - region_score_threshold: number -} - -interface MeshIntelligenceConfig { - enabled: boolean - regions: RegionAnchor[] - locality_radius_miles: number - offline_threshold_hours: number - packet_threshold: number - battery_warning_percent: number - critical_nodes: string[] - alert_channel: number - alert_cooldown_minutes: number - alert_rules: AlertRulesConfig -} - -interface NWSConfig { - enabled: boolean - tick_seconds: number - areas: string[] - severity_min: string - user_agent: string -} - -interface EnvironmentalConfig { - enabled: boolean - nws_zones: string[] - nws: NWSConfig - swpc: { enabled: boolean } - ducting: { enabled: boolean; tick_seconds: number; latitude: number; longitude: number } - fires: { enabled: boolean; tick_seconds: number; state: string } - avalanche: { enabled: boolean; tick_seconds: number; center_ids: string[]; season_months: number[] } - usgs: { enabled: boolean; tick_seconds: number; sites: string[] } - traffic: { enabled: boolean; tick_seconds: number; api_key: string; corridors: { name: string; lat: number; lon: number }[] } - roads511: { enabled: boolean; tick_seconds: number; api_key: string; base_url: string; endpoints: string[]; bbox: number[] } - firms: { enabled: boolean; tick_seconds: number; map_key: string; source: string; bbox: number[]; day_range: number; confidence_min: string; proximity_km: number } -} - -interface DashboardConfig { - enabled: boolean - port: number - host: string -} - -interface FullConfig { - bot: BotConfig - connection: ConnectionConfig - response: ResponseConfig - history: HistoryConfig - memory: MemoryConfig - context: ContextConfig - commands: CommandsConfig - llm: LLMConfig - weather: WeatherConfig - meshmonitor: MeshMonitorConfig - knowledge: KnowledgeConfig - mesh_sources: MeshSourceConfig[] - mesh_intelligence: MeshIntelligenceConfig - environmental: EnvironmentalConfig - dashboard: DashboardConfig -} - -type SectionKey = keyof FullConfig - -const SECTIONS: { key: SectionKey; label: string; icon: typeof Settings }[] = [ - { key: 'bot', label: 'Bot', icon: Bot }, - { key: 'connection', label: 'Connection', icon: Wifi }, - { key: 'response', label: 'Response', icon: MessageSquare }, - { key: 'history', label: 'History', icon: Database }, - { key: 'memory', label: 'Memory', icon: Brain }, - { key: 'context', label: 'Context', icon: Eye }, - { key: 'commands', label: 'Commands', icon: Terminal }, - { key: 'llm', label: 'LLM', icon: Cpu }, - { key: 'weather', label: 'Weather', icon: Cloud }, - { key: 'meshmonitor', label: 'MeshMonitor', icon: Radio }, - { key: 'knowledge', label: 'Knowledge', icon: BookOpen }, - { key: 'mesh_sources', label: 'Mesh Sources', icon: Layers }, - { key: 'mesh_intelligence', label: 'Intelligence', icon: Activity }, - { key: 'environmental', label: 'Environmental', icon: Thermometer }, - { key: 'dashboard', label: 'Dashboard', icon: LayoutDashboard }, -] - -// Form components -function TextInput({ label, value, onChange, type = 'text', placeholder = '', helper = '' }: { - label: string - value: string - onChange: (v: string) => void - type?: string - placeholder?: string - helper?: string -}) { - const [showPassword, setShowPassword] = useState(false) - const isPassword = type === 'password' - - return ( -
- -
- onChange(e.target.value)} - placeholder={placeholder} - className="w-full px-3 py-2 bg-[#0a0e17] border border-[#1e2a3a] rounded text-sm text-slate-200 font-mono focus:outline-none focus:border-accent placeholder-slate-600" - /> - {isPassword && ( - - )} -
- {helper &&

{helper}

} -
- ) -} - -function NumberInput({ label, value, onChange, min, max, step = 1, helper = '' }: { - label: string - value: number - onChange: (v: number) => void - min?: number - max?: number - step?: number - helper?: string -}) { - return ( -
- - onChange(Number(e.target.value))} - min={min} - max={max} - step={step} - className="w-full px-3 py-2 bg-[#0a0e17] border border-[#1e2a3a] rounded text-sm text-slate-200 font-mono focus:outline-none focus:border-accent" - /> - {helper &&

{helper}

} -
- ) -} - -function Toggle({ label, checked, onChange, helper = '' }: { - label: string - checked: boolean - onChange: (v: boolean) => void - helper?: string -}) { - return ( -
-
- {label} - {helper &&

{helper}

} -
- -
- ) -} - -function SelectInput({ label, value, onChange, options, helper = '' }: { - label: string - value: string - onChange: (v: string) => void - options: { value: string; label: string }[] - helper?: string -}) { - return ( -
- - - {helper &&

{helper}

} -
- ) -} - -function TextArea({ label, value, onChange, rows = 4, helper = '' }: { - label: string - value: string - onChange: (v: string) => void - rows?: number - helper?: string -}) { - return ( -
- -