diff --git a/dashboard-frontend/src/pages/Environment.tsx b/dashboard-frontend/src/pages/Environment.tsx
index 6960a01..30ffa16 100644
--- a/dashboard-frontend/src/pages/Environment.tsx
+++ b/dashboard-frontend/src/pages/Environment.tsx
@@ -388,6 +388,28 @@ export default function Environment() {
)}
+ {/* Central Connection (v0.5) -- NATS source for adapters set to central */}
+ {env.central && (
+
+
+
+
Central Connection
+
NATS JetStream source for any adapter set to "central"
+
+
up({ central: { ...env.central!, enabled: v } })} />
+
+
+ up({ central: { ...env.central!, url: v } })}
+ placeholder="nats://central.echo6.mesh:4222" />
+ up({ central: { ...env.central!, durable: v } })}
+ placeholder="meshai-v04" />
+
+
+ )}
+
{/* Family tabs */}
{FAMILIES.map(({ key, label, icon: Icon }) => (
diff --git a/dashboard-frontend/src/pages/Notifications.tsx b/dashboard-frontend/src/pages/Notifications.tsx
index 5556d55..34d85dc 100644
--- a/dashboard-frontend/src/pages/Notifications.tsx
+++ b/dashboard-frontend/src/pages/Notifications.tsx
@@ -3,7 +3,8 @@ import {
Save, RotateCcw, RefreshCw, Plus, Trash2, ChevronDown, ChevronRight,
Check, X, Eye as EyeIcon, EyeOff, Send, Clock, Zap,
Calendar, AlertTriangle, Copy, Moon, AlertCircle, Layers,
- Wifi, WifiOff, Mail, Globe, Radio, MessageSquare
+ Wifi, WifiOff, Mail, Globe, Radio, MessageSquare,
+ Activity, Cloud, Flame, Car, Snowflake, Mountain, MapPin
} from 'lucide-react'
import ChannelPicker from '@/components/ChannelPicker'
import NodePicker from '@/components/NodePicker'
@@ -37,12 +38,33 @@ interface NotificationRuleConfig {
override_quiet: boolean
}
+interface NotificationToggle {
+ name: string
+ enabled: boolean
+ min_severity: string
+ regions: string[]
+ severity_channels: Record
+ quiet_hours_override: boolean
+ broadcast_channel: number | null
+ node_ids: string[]
+ smtp_host: string
+ smtp_port: number
+ smtp_user: string
+ smtp_password: string
+ smtp_tls: boolean
+ from_address: string
+ recipients: string[]
+ webhook_url: string
+ webhook_headers: Record
+}
+
interface NotificationsConfig {
enabled: boolean
quiet_hours_enabled: boolean
quiet_hours_start: string
quiet_hours_end: string
rules: NotificationRuleConfig[]
+ toggles?: Record
}
interface AlertCategory {
@@ -1340,6 +1362,105 @@ function NotificationRuleCard({
}
// Main Notifications Page Component
+const TOGGLE_FAMILY_META: { key: string; label: string; Icon: typeof Activity }[] = [
+ { key: 'mesh_health', label: 'Mesh Health', Icon: Activity },
+ { key: 'weather', label: 'Weather', Icon: Cloud },
+ { key: 'fire', label: 'Fire', Icon: Flame },
+ { key: 'rf_propagation', label: 'RF Propagation', Icon: Radio },
+ { key: 'roads', label: 'Roads', Icon: Car },
+ { key: 'avalanche', label: 'Avalanche', Icon: Snowflake },
+ { key: 'seismic', label: 'Seismic', Icon: Mountain },
+ { key: 'tracking', label: 'Tracking', Icon: MapPin },
+]
+const TOGGLE_CHANNELS = ['digest', 'mesh_broadcast', 'mesh_dm', 'email', 'webhook']
+const TOGGLE_SEVERITIES = ['routine', 'priority', 'immediate']
+
+function MasterToggles({ toggles, onChange }: {
+ toggles: Record
+ onChange: (t: Record) => void
+}) {
+ const [expanded, setExpanded] = useState(null)
+ const upd = (fam: string, patch: Partial) =>
+ onChange({ ...toggles, [fam]: { ...(toggles[fam] || {}), name: fam, ...patch } as NotificationToggle })
+ return (
+
+
+ Master Toggles
+
+
+
+ {TOGGLE_FAMILY_META.map(({ key, label, Icon }) => {
+ const t = (toggles[key] || ({} as NotificationToggle))
+ const isOpen = expanded === key
+ const chanCount = Object.values(t.severity_channels || {}).reduce((n, arr) => n + ((arr as string[])?.length || 0), 0)
+ const regionCount = (t.regions || []).length
+ return (
+
+
+
+ upd(key, { enabled: v })} />
+
+ {!isOpen && (
+
+ {t.enabled
+ ? `${regionCount || 'all'} region${regionCount === 1 ? '' : 's'}, ${chanCount} channel${chanCount === 1 ? '' : 's'} at ${t.min_severity || 'priority'}+`
+ : 'OFF'}
+
+ )}
+ {isOpen && (
+
+
upd(key, { min_severity: v })} />
+ Severity → channels
+
+ upd(key, { regions: v })} placeholder="Add region..." />
+ upd(key, { quiet_hours_override: v })} />
+ Channel config
+ upd(key, { broadcast_channel: v })} />
+ upd(key, { node_ids: v })} placeholder="!nodeid" />
+ upd(key, { recipients: v })} placeholder="ops@example.com" />
+ upd(key, { smtp_host: v })} placeholder="smtp.example.com" />
+ upd(key, { smtp_port: v })} />
+ upd(key, { webhook_url: v })} placeholder="https://..." />
+
+ )}
+
+ )
+ })}
+
+
+ )
+}
+
+
export default function Notifications() {
const [config, setConfig] = useState(null)
const [originalConfig, setOriginalConfig] = useState(null)
@@ -1802,6 +1923,14 @@ export default function Notifications() {
)}
+ {/* Master Toggles */}
+ {config.toggles && (
+