const express = require('express'); /** * Server-Sent Events route factory * Pushes real-time updates to connected dashboard clients * @param {Object} deps - Dependencies * @param {Object} deps.resourceMonitor - Container resource monitor * @param {Object} deps.healthChecker - Health checker * @param {Object} deps.updateManager - Update manager * @param {Function} deps.logError - Error logging function * @param {Object} deps.dependencyManager - Dependency manager for restart chain events * @returns {express.Router} */ module.exports = function({ resourceMonitor, healthChecker, updateManager, logError, dependencyManager, autoRestartManager, driftDetector, sslMonitor, dnsPropagationChecker }) { const router = express.Router(); const clients = new Set(); function broadcast(event, data) { const msg = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`; for (const res of clients) { try { res.write(msg); } catch (_) { clients.delete(res); } } } // --- Wire up EventEmitter listeners --- // Resource monitor events if (resourceMonitor) { resourceMonitor.on('alert', (data) => { broadcast('resource-alert', data); }); resourceMonitor.on('auto-restart', (data) => { broadcast('auto-restart', data); }); } // Health checker events if (healthChecker) { healthChecker.on('status-check', (data) => { broadcast('status-change', { serviceId: data.serviceId, name: data.name, status: data.status, responseTime: data.responseTime, timestamp: data.timestamp }); }); healthChecker.on('incident-created', (data) => { broadcast('incident', { type: 'created', ...data }); }); healthChecker.on('incident-resolved', (data) => { broadcast('incident', { type: 'resolved', ...data }); }); } // Update manager events if (updateManager) { updateManager.on('update-available', (data) => { broadcast('update-available', data); }); updateManager.on('update-start', (data) => { broadcast('update-start', data); }); updateManager.on('update-complete', (data) => { broadcast('update-complete', data); }); updateManager.on('update-failed', (data) => { broadcast('update-failed', data); }); updateManager.on('auto-update-start', (data) => { broadcast('auto-update-start', data); }); updateManager.on('auto-update-complete', (data) => { broadcast('auto-update-complete', data); }); } // Dependency manager events if (dependencyManager) { dependencyManager.on('dependency-restart-start', (data) => { broadcast('dependency-restart-start', data); }); dependencyManager.on('dependency-restart-progress', (data) => { broadcast('dependency-restart-progress', data); }); dependencyManager.on('dependency-restart-complete', (data) => { broadcast('dependency-restart-complete', data); }); dependencyManager.on('dependency-restart-failed', (data) => { broadcast('dependency-restart-failed', data); }); } // Auto-restart manager events if (autoRestartManager) { autoRestartManager.on('auto-restart-attempt', (data) => broadcast('auto-restart-attempt', data)); autoRestartManager.on('auto-restart-success', (data) => broadcast('auto-restart-success', data)); autoRestartManager.on('auto-restart-failed', (data) => broadcast('auto-restart-failed', data)); autoRestartManager.on('auto-restart-max-reached', (data) => broadcast('auto-restart-max-reached', data)); } // Config drift detector events if (driftDetector) { driftDetector.on('drift-detected', (data) => broadcast('drift-detected', data)); } // SSL monitor events if (sslMonitor) { sslMonitor.on('cert-expiring', (data) => broadcast('cert-expiring', data)); sslMonitor.on('cert-critical', (data) => broadcast('cert-critical', data)); } // DNS propagation checker events if (dnsPropagationChecker) { dnsPropagationChecker.on('propagation-check', (data) => broadcast('dns-propagation-check', data)); dnsPropagationChecker.on('propagation-complete', (data) => broadcast('dns-propagation-complete', data)); dnsPropagationChecker.on('propagation-timeout', (data) => broadcast('dns-propagation-timeout', data)); } // SSE endpoint router.get('/stream', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'X-Accel-Buffering': 'no', }); // Send initial connected event res.write(`event: connected\ndata: ${JSON.stringify({ clients: clients.size + 1 })}\n\n`); clients.add(res); // Heartbeat every 30s const heartbeat = setInterval(() => { try { res.write(': heartbeat\n\n'); } catch (_) { cleanup(); } }, 30000); function cleanup() { clearInterval(heartbeat); clients.delete(res); } req.on('close', cleanup); req.on('error', cleanup); }); // Client count (useful for debugging) router.get('/clients', (req, res) => { res.json({ success: true, count: clients.size }); }); return router; };