mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-06-11 17:34:44 +02:00
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:
parent
10328686e2
commit
3fa7b9fe5e
10 changed files with 551 additions and 107 deletions
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue