forked from Rockachopa/Timmy-time-dashboard
feat(ui): wire WebSocket live feed into HTMX dashboard
- Fix swarm_live.html WebSocket URL from /swarm/ws to /swarm/live (matching the actual endpoint in swarm_ws.py) - Update handleMessage() to process individual swarm events (agent_joined, task_posted, bid_submitted, task_assigned, etc.) in addition to bulk state snapshots - Add refreshStats() helper that fetches /swarm REST endpoint to update stat counters after each event - Add GET /swarm/live page route to render the swarm_live.html template - Add SWARM and MOBILE navigation links to base.html header (fixes UX-01: /mobile route not in desktop nav)
This commit is contained in:
@@ -24,6 +24,15 @@ async def swarm_status():
|
||||
return coordinator.status()
|
||||
|
||||
|
||||
@router.get("/live", response_class=HTMLResponse)
|
||||
async def swarm_live_page(request: Request):
|
||||
"""Render the live swarm dashboard page."""
|
||||
return templates.TemplateResponse(
|
||||
"swarm_live.html",
|
||||
{"request": request, "page_title": "Swarm Live"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/agents")
|
||||
async def list_swarm_agents():
|
||||
"""List all registered swarm agents."""
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
<span class="mc-subtitle">MISSION CONTROL</span>
|
||||
</div>
|
||||
<div class="mc-header-right">
|
||||
<a href="/swarm/live" class="mc-test-link">SWARM</a>
|
||||
<a href="/mobile" class="mc-test-link">MOBILE</a>
|
||||
<a href="/mobile-test" class="mc-test-link">TEST</a>
|
||||
<span class="mc-time" id="clock"></span>
|
||||
</div>
|
||||
|
||||
@@ -56,7 +56,7 @@ const maxReconnectInterval = 30000;
|
||||
|
||||
function connect() {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
ws = new WebSocket(`${protocol}//${window.location.host}/swarm/ws`);
|
||||
ws = new WebSocket(`${protocol}//${window.location.host}/swarm/live`);
|
||||
|
||||
ws.onopen = function() {
|
||||
console.log('WebSocket connected');
|
||||
@@ -88,20 +88,48 @@ function connect() {
|
||||
}
|
||||
|
||||
function handleMessage(message) {
|
||||
// Handle structured state snapshots (initial_state / state_update)
|
||||
if (message.type === 'initial_state' || message.type === 'state_update') {
|
||||
const data = message.data;
|
||||
|
||||
// Update stats
|
||||
document.getElementById('stat-agents').textContent = data.agents.total;
|
||||
document.getElementById('stat-active').textContent = data.agents.active;
|
||||
document.getElementById('stat-tasks').textContent = data.tasks.active;
|
||||
|
||||
// Update agents list
|
||||
updateAgentsList(data.agents.list);
|
||||
|
||||
// Update auctions list
|
||||
updateAuctionsList(data.auctions.list);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle individual swarm events broadcast by ws_manager
|
||||
const evt = message.event || message.type || '';
|
||||
const data = message.data || message;
|
||||
|
||||
if (evt === 'agent_joined') {
|
||||
addLog('Agent joined: ' + (data.name || data.agent_id || ''), 'success');
|
||||
refreshStats();
|
||||
} else if (evt === 'agent_left') {
|
||||
addLog('Agent left: ' + (data.name || data.agent_id || ''), 'warning');
|
||||
refreshStats();
|
||||
} else if (evt === 'task_posted') {
|
||||
addLog('Task posted: ' + (data.description || data.task_id || '').slice(0, 60), 'info');
|
||||
refreshStats();
|
||||
} else if (evt === 'bid_submitted') {
|
||||
addLog('Bid: ' + (data.agent_id || '').slice(0, 8) + ' bid ' + (data.bid_sats || '?') + ' sats', 'info');
|
||||
} else if (evt === 'task_assigned') {
|
||||
addLog('Task assigned to ' + (data.agent_id || '').slice(0, 8), 'success');
|
||||
refreshStats();
|
||||
} else if (evt === 'task_completed') {
|
||||
addLog('Task completed by ' + (data.agent_id || '').slice(0, 8), 'success');
|
||||
refreshStats();
|
||||
}
|
||||
}
|
||||
|
||||
function refreshStats() {
|
||||
// Fetch current swarm status via REST and update the stat counters
|
||||
fetch('/swarm').then(r => r.json()).then(data => {
|
||||
document.getElementById('stat-agents').textContent = data.agents || 0;
|
||||
document.getElementById('stat-active').textContent = data.agents_busy || 0;
|
||||
document.getElementById('stat-tasks').textContent = (data.tasks_pending || 0) + (data.tasks_running || 0);
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// Safe text setter — avoids XSS when inserting user/server data into DOM
|
||||
|
||||
Reference in New Issue
Block a user