From d49e417400c58584bece3c2f6daa9d65f31fc814 Mon Sep 17 00:00:00 2001 From: Matt Johnson Date: Thu, 4 Jun 2026 03:08:15 +0000 Subject: [PATCH] feat(dashboard): v0.5.6 -- Advanced Rules editor polish (grouped categories, region_scope, name tooltip, smart activity badge) Frontend-only polish to the Advanced Rules section in Notifications.tsx. Master Toggles (v0.5.0) and safe-mode are untouched. FIX 1 -- Grouped Alert Categories. Replace the flat ~45-item checkbox list with a per-family grouped picker. Each family (mesh_health/weather/fire/rf_propagation/roads/avalanche/seismic/tracking) is a collapsible section with the lucide icon used by Master Toggles, a category count in the header, and per-family "All" / "Clear" bulk-toggle buttons. Families that already have selections expand by default. Categories whose toggle field does not match a known family fall into an "Other" group at the bottom. Uses the backend toggle field already provided by /api/notifications/categories. FIX 2 -- region_scope multi-select. Adds a REGIONS block between WHEN and SEND VIA, wired to rule.region_scope (NotificationRuleConfig backend field has existed since v0.5.0). Fetches /api/regions alongside config/categories. Pill-style toggle buttons; empty selection means all regions (backward compat). region_scope added to createDefaultRule; addFromTemplate merges over createDefaultRule so future config fields do not need to be backfilled into every literal template. FIX 3 -- truncated rule name hover tooltip. Adds title={rule.name} to the collapsed-header name span so long rule names stay readable on hover. FIX 4 -- Smart activity badge. Replaces the unconditional "Never fired" badge with state-aware variants: gray "Disabled" when rule.enabled is false; green "Active" when fire_count > 0 and last_fired within 7 days; yellow "Idle (no recent activity)" when fire_count > 0 but last_fired > 7d ago; gray "No activity yet" when never fired (without implying breakage). Same badge Tailwind shape as before; last_fired surfaces via the title tooltip. Backend untouched. npm run build (tsc strict) clean. PYTHONPATH=. pytest -q: 328 passed (unchanged from v0.5.5). Safe-mode preserved (master off, all toggles off, all adapters native, central disabled). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/pages/Notifications.tsx | 254 +++++++++++++++-- ...{index-DjhQa8Mv.css => index-CHkr5tDL.css} | 2 +- .../{index-B24tHcYj.js => index-DwsA2DLM.js} | 264 +++++++++--------- meshai/dashboard/static/index.html | 4 +- 4 files changed, 360 insertions(+), 164 deletions(-) rename meshai/dashboard/static/assets/{index-DjhQa8Mv.css => index-CHkr5tDL.css} (51%) rename meshai/dashboard/static/assets/{index-B24tHcYj.js => index-DwsA2DLM.js} (61%) diff --git a/dashboard-frontend/src/pages/Notifications.tsx b/dashboard-frontend/src/pages/Notifications.tsx index 34d85dc..6edf862 100644 --- a/dashboard-frontend/src/pages/Notifications.tsx +++ b/dashboard-frontend/src/pages/Notifications.tsx @@ -36,6 +36,7 @@ interface NotificationRuleConfig { webhook_headers: Record cooldown_minutes: number override_quiet: boolean + region_scope: string[] } interface NotificationToggle { @@ -73,6 +74,12 @@ interface AlertCategory { description: string default_severity: string example_message: string + toggle?: string +} + +interface RegionInfo { + name: string + local_name?: string } interface RuleStats { @@ -719,6 +726,7 @@ function NotificationRuleCard({ rule, ruleIndex, categories, + regions, quietHoursEnabled, onChange, onDelete, @@ -728,6 +736,7 @@ function NotificationRuleCard({ rule: NotificationRuleConfig ruleIndex: number categories: AlertCategory[] + regions: RegionInfo[] quietHoursEnabled: boolean onChange: (r: NotificationRuleConfig) => void onDelete: () => void @@ -793,6 +802,26 @@ function NotificationRuleCard({ } } + const selectManyCategories = (catIds: string[], action: 'add' | 'remove') => { + const current = rule.categories || [] + if (action === 'add') { + const merged = Array.from(new Set([...current, ...catIds])) + onChange({ ...rule, categories: merged }) + } else { + const drop = new Set(catIds) + onChange({ ...rule, categories: current.filter(c => !drop.has(c)) }) + } + } + + const toggleRegion = (regionName: string) => { + const current = rule.region_scope || [] + if (current.includes(regionName)) { + onChange({ ...rule, region_scope: current.filter(r => r !== regionName) }) + } else { + onChange({ ...rule, region_scope: [...current, regionName] }) + } + } + const toggleDay = (day: string) => { const current = rule.schedule_days || [] if (current.includes(day)) { @@ -914,7 +943,7 @@ function NotificationRuleCard({ ) : ( )} - {rule.name || 'New Rule'} + {rule.name || 'New Rule'} {!expanded && (