diff --git a/bin/lib/admin.js b/bin/lib/admin.js index defc15f..6ab7d5f 100644 --- a/bin/lib/admin.js +++ b/bin/lib/admin.js @@ -7,6 +7,7 @@ import { WebSocketServer, WebSocket } from 'ws'; import { getLogsForAPI, LogFileWatcher } from './logs.js'; import { startService, stopService, restartService, getServiceStatus, getAllServiceStatuses, validateServiceCanRun } from './service-manager.js'; import { initializePlugins, callHook } from './plugin-system.js'; +import { getResourceMonitor } from './resources.js'; // ws helper function sendWebSocketMessage(ws, message) { @@ -23,6 +24,7 @@ function sendWebSocketMessage(ws, message) { // Global log watcher instance let globalLogWatcher = null; let wsServer = null; +let globalResourceMonitor = null; async function handleWebSocketMessage(ws, message) { if (message.type === 'start_log_stream') { @@ -43,6 +45,70 @@ async function handleWebSocketMessage(ws, message) { sendWebSocketMessage(ws, { type: 'log_data', service: ws.serviceFilter, logs }); } else if (message.type === 'stop_log_stream') { ws.serviceFilter = null; + } else if (message.type === 'start_metrics_stream') { + console.log('🔧 WebSocket: start_metrics_stream received, service:', message.service); + ws.metricsFilter = message.service || 'all'; + console.log('🔧 WebSocket: metricsFilter set to:', ws.metricsFilter); + if (!globalResourceMonitor) { + sendWebSocketMessage(ws, { type: 'error', message: 'Resource monitor not initialized' }); + return; + } + // Send current metrics in the expected format + const serviceFilter = ws.metricsFilter === 'all' ? null : ws.metricsFilter; + const currentMetrics = globalResourceMonitor.getCurrentMetrics(serviceFilter); + console.log('🔧 WebSocket: getCurrentMetrics returned:', serviceFilter ? 'single service' : Object.keys(currentMetrics || {})); + const services = []; + + // Convert metrics to array format expected by frontend + if (serviceFilter && currentMetrics) { + // Single service requested + services.push({ + serviceName: serviceFilter, + timestamp: currentMetrics.timestamp, + cpu: { usage: currentMetrics.cpu || 0 }, + memory: { percentage: currentMetrics.memory || 0 }, + network: currentMetrics.network || { rx: 0, tx: 0 }, + status: currentMetrics.status || 'unknown' + }); + } else if (!serviceFilter && currentMetrics) { + // All services requested + for (const [serviceName, metrics] of Object.entries(currentMetrics)) { + if (metrics) { + services.push({ + serviceName: serviceName, + timestamp: metrics.timestamp, + cpu: { usage: metrics.cpu || 0 }, + memory: { percentage: metrics.memory || 0 }, + network: metrics.network || { rx: 0, tx: 0 }, + status: metrics.status || 'unknown' + }); + } + } + } + + console.log('🔧 WebSocket: Sending metrics_data with', services.length, 'services'); + sendWebSocketMessage(ws, { + type: 'metrics_data', + service: ws.metricsFilter, + services: services, + systemInfo: globalResourceMonitor.getSystemInfo() + }); + } else if (message.type === 'stop_metrics_stream') { + ws.metricsFilter = null; + } else if (message.type === 'get_metrics_history') { + if (!globalResourceMonitor) { + sendWebSocketMessage(ws, { type: 'error', message: 'Resource monitor not initialized' }); + return; + } + const history = globalResourceMonitor.getMetricsHistory(message.service, { + since: message.since, + limit: message.limit || 100 + }); + sendWebSocketMessage(ws, { + type: 'metrics_history', + service: message.service, + history + }); } } @@ -72,6 +138,34 @@ function broadcastLogEvent(event, payload) { }); } +// Set up listener for real-time metrics updates +function broadcastMetricsEvent(event, payload) { + if (!wsServer) return; + console.log(`🔧 Broadcasting ${event} to ${wsServer.clients.size} clients`); + wsServer.clients.forEach(ws => { + if (ws.readyState !== WebSocket.OPEN) return; + console.log(`🔧 Client metricsFilter: ${ws.metricsFilter}`); + // Only send to clients that requested metrics stream + if (!ws.metricsFilter) return; + // Filter by service if client requested specific service + if (ws.metricsFilter !== 'all' && payload.services) { + const filteredServices = payload.services.filter(s => s.serviceName === ws.metricsFilter); + if (filteredServices.length === 0) return; + payload = { ...payload, services: filteredServices }; + } + + if (event === 'metricsUpdate') { + console.log(`🔧 Sending metrics_update to client with ${payload.services?.length || 0} services`); + sendWebSocketMessage(ws, { + type: 'metrics_update', + timestamp: payload.timestamp, + services: payload.services, + system: payload.system + }); + } + }); +} + async function checkServiceStatus(service) { return new Promise((resolve) => { const timeout = setTimeout(() => { @@ -145,6 +239,7 @@ function generateDashboardHTML(servicesWithStatus, refreshInterval = 5000) {