diff --git a/routes/controller.py b/routes/controller.py index 9428bbd..80e74b0 100644 --- a/routes/controller.py +++ b/routes/controller.py @@ -267,6 +267,68 @@ def get_agent_status(agent_id: int): }), 503 +@controller_bp.route('/agents/health', methods=['GET']) +def check_all_agents_health(): + """ + Check health of all registered agents in one call. + + More efficient than checking each agent individually. + Returns health status, response time, and running modes for each agent. + """ + agents_list = list_agents(active_only=True) + results = [] + + for agent in agents_list: + result = { + 'id': agent['id'], + 'name': agent['name'], + 'healthy': False, + 'response_time_ms': None, + 'running_modes': [], + 'error': None + } + + try: + client = create_client_from_agent(agent) + + # Time the health check + start_time = time.time() + is_healthy = client.health_check() + response_time = (time.time() - start_time) * 1000 + + result['healthy'] = is_healthy + result['response_time_ms'] = round(response_time, 1) + + if is_healthy: + # Update last_seen in database + update_agent(agent['id'], update_last_seen=True) + + # Also fetch running modes + try: + status = client.get_status() + result['running_modes'] = status.get('running_modes', []) + result['running_modes_detail'] = status.get('running_modes_detail', {}) + except Exception: + pass # Status fetch is optional + + except AgentConnectionError as e: + result['error'] = f'Connection failed: {str(e)}' + except AgentHTTPError as e: + result['error'] = f'HTTP error: {str(e)}' + except Exception as e: + result['error'] = str(e) + + results.append(result) + + return jsonify({ + 'status': 'success', + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'agents': results, + 'total': len(results), + 'healthy_count': sum(1 for r in results if r['healthy']) + }) + + # ============================================================================= # Proxy Operations - Forward requests to agents # ============================================================================= diff --git a/static/js/core/agents.js b/static/js/core/agents.js index e2458ed..15e9a8a 100644 --- a/static/js/core/agents.js +++ b/static/js/core/agents.js @@ -12,6 +12,150 @@ let multiAgentMode = false; // Show combined results from all agents let multiAgentPollInterval = null; let agentRunningModes = []; // Track agent's running modes for conflict detection let agentRunningModesDetail = {}; // Track device info per mode (for multi-SDR agents) +let healthCheckInterval = null; // Health monitoring interval +let agentHealthStatus = {}; // Cache of health status per agent ID + +// ============== AGENT HEALTH MONITORING ============== + +/** + * Start periodic health monitoring for all agents. + * Runs every 30 seconds to check agent health status. + */ +function startHealthMonitoring() { + // Don't start if already running + if (healthCheckInterval) return; + + // Initial check + checkAllAgentsHealth(); + + // Start periodic checks every 30 seconds + healthCheckInterval = setInterval(checkAllAgentsHealth, 30000); + console.log('[AgentManager] Health monitoring started (30s interval)'); +} + +/** + * Stop health monitoring. + */ +function stopHealthMonitoring() { + if (healthCheckInterval) { + clearInterval(healthCheckInterval); + healthCheckInterval = null; + console.log('[AgentManager] Health monitoring stopped'); + } +} + +/** + * Check health of all registered agents in one efficient call. + */ +async function checkAllAgentsHealth() { + if (agents.length === 0) return; + + try { + const response = await fetch('/controller/agents/health'); + const data = await response.json(); + + if (data.status === 'success' && data.agents) { + // Update health status cache and UI + data.agents.forEach(agentHealth => { + const previousHealth = agentHealthStatus[agentHealth.id]; + agentHealthStatus[agentHealth.id] = agentHealth; + + // Update agent in local list + const agent = agents.find(a => a.id === agentHealth.id); + if (agent) { + const wasHealthy = agent.healthy !== false; + agent.healthy = agentHealth.healthy; + agent.response_time_ms = agentHealth.response_time_ms; + agent.running_modes = agentHealth.running_modes || []; + agent.running_modes_detail = agentHealth.running_modes_detail || {}; + + // Log status change + if (wasHealthy !== agentHealth.healthy) { + console.log(`[AgentManager] ${agent.name} is now ${agentHealth.healthy ? 'ONLINE' : 'OFFLINE'}`); + + // Show notification for status change + if (!agentHealth.healthy && typeof showNotification === 'function') { + showNotification(`Agent "${agent.name}" went offline`, 'warning'); + } + } + } + }); + + // Update UI + updateAgentHealthUI(); + + // If current agent is selected, sync mode warnings + if (currentAgent !== 'local') { + const currentHealth = agentHealthStatus[currentAgent]; + if (currentHealth) { + agentRunningModes = currentHealth.running_modes || []; + agentRunningModesDetail = currentHealth.running_modes_detail || {}; + showAgentModeWarnings(agentRunningModes, agentRunningModesDetail); + } + } + } + } catch (error) { + console.error('[AgentManager] Health check failed:', error); + } +} + +/** + * Update the UI to reflect current health status. + */ +function updateAgentHealthUI() { + const selector = document.getElementById('agentSelect'); + if (!selector) return; + + // Update each option in selector + agents.forEach(agent => { + const option = selector.querySelector(`option[value="${agent.id}"]`); + if (option) { + const health = agentHealthStatus[agent.id]; + const isHealthy = health ? health.healthy : agent.healthy !== false; + const status = isHealthy ? '●' : '○'; + const latency = health?.response_time_ms ? ` (${health.response_time_ms}ms)` : ''; + option.textContent = `${status} ${agent.name}${latency}`; + option.dataset.healthy = isHealthy; + } + }); + + // Update status display for current agent + updateAgentStatus(); + + // Update health panel if it exists + updateHealthPanel(); +} + +/** + * Update the optional health panel showing all agents. + */ +function updateHealthPanel() { + const panel = document.getElementById('agentHealthPanel'); + if (!panel) return; + + if (agents.length === 0) { + panel.innerHTML = '