feat(dashboard): Add dynamic channel and node pickers

- Add GET /api/channels endpoint for live radio channel data
- Create ChannelPicker component (single/multi-select from live channels)
- Create NodePicker component (searchable multi-select from mesh nodes)
- Replace manual inputs in Config with data-driven pickers
- Update Notifications to use pickers for mesh broadcast/DM
- Resolve node names in Alerts subscriptions display

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
K7ZVX 2026-05-13 07:07:05 +00:00
commit 3fa7b9fe5e
10 changed files with 551 additions and 107 deletions

View file

@ -354,3 +354,50 @@ async def get_edges(request: Request):
})
return edges
@router.get("/channels")
async def get_channels(request: Request):
"""Get radio channels from the connected Meshtastic interface."""
connector = getattr(request.app.state, "connector", None)
if not connector or not connector.connected:
return []
try:
interface = connector._interface
if not interface or not hasattr(interface, "localNode"):
return []
local_node = interface.localNode
if not local_node or not hasattr(local_node, "channels"):
return []
channels = []
for ch in local_node.channels:
if ch is None:
continue
# Get channel settings
settings = getattr(ch, "settings", None)
name = getattr(settings, "name", "") if settings else ""
role_val = getattr(ch, "role", 0)
# Map role enum to string
role_map = {0: "DISABLED", 1: "PRIMARY", 2: "SECONDARY"}
role = role_map.get(role_val, "UNKNOWN")
channels.append({
"index": ch.index,
"name": name or f"Channel {ch.index}",
"role": role,
"enabled": role_val != 0,
})
return channels
except Exception as e:
import logging
logging.getLogger(__name__).warning(f"Failed to get channels: {e}")
return []

View file

@ -113,6 +113,7 @@ async def start_dashboard(meshai_instance: "MeshAI") -> DashboardBroadcaster:
app.state.env_store = getattr(meshai_instance, "env_store", None)
app.state.subscription_manager = meshai_instance.subscription_manager
app.state.notification_router = getattr(meshai_instance, "notification_router", None)
app.state.connector = meshai_instance.connector
# Create broadcaster and attach to app state
broadcaster = DashboardBroadcaster()

View file

@ -8,8 +8,8 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<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">
<script type="module" crossorigin src="/assets/index-DGtsDLP7.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D1Pqs_mG.css">
<script type="module" crossorigin src="/assets/index-BNjrbmGz.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-so1NV9Au.css">
</head>
<body>
<div id="root"></div>