Resolves 24 conflicts between Hermes (DC-008/009/010 + response-helper
envelope standardization) and Krystie (DC-005 src/ refactor path fixes,
DC-006 TOTP integration, DC-007 new test suites, cloud backup
destinations).
Conflict resolutions:
- src/utils/logging.js: took ours (consumers depend on logError/
safeErrorMessage/createLogger exports)
- src/config/site.js: merged (her factored validateAndLogConfig +
applyConfigFields helpers)
- src/context/dns.js: took hers (admin/readonly role iteration for
write operations)
- src/utilities/backup-
manager.js: took hers (Dropbox/WebDAV/SFTP cloud feature)
- status/dist/*, status/
sw.js: took hers (minified bundles + newer SW cache)
Additional fix (post-merge regression):
- src/monitoring/health-checker.js: fixed DC-005 path miss —
'require(./platform-paths)' → 'require(../../platform-paths)'
Test status: 921/922 passing. One known failure in logging.test.js
(async file-handle timing) tracked as follow-up.
160 lines
5.4 KiB
JavaScript
160 lines
5.4 KiB
JavaScript
const express = require('express');
|
|
const { ok } = require('../src/utils/responses');
|
|
|
|
/**
|
|
* 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
|
|
* @param {Object} deps.autoRestartManager - Auto-restart manager
|
|
* @param {Object} deps.driftDetector - Config drift detector
|
|
* @param {Object} deps.sslMonitor - SSL cert expiration monitor
|
|
* @param {Object} deps.dnsPropagationChecker - DNS propagation checker
|
|
* @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) => {
|
|
ok(res, { count: clients.size });
|
|
});
|
|
|
|
return router;
|
|
};
|