feat: add 7 new features — exec shell, SSE events, compose import, docker resources, resource limits, email notifications, auto-updates
- Container exec/shell via WebSocket + xterm.js (subtle >_ button on cards) - Live dashboard updates via SSE (resource alerts, health changes, update notices) - Docker Compose import with YAML parsing, preview, and dependency-ordered deploy - Volume & network management modal with disk usage overview - CPU/memory resource limits on deploy and live update - Email SMTP notifications (nodemailer) alongside Discord/Telegram/ntfy - Scheduled auto-update scheduler with maintenance windows (daily/weekly/monthly) New deps: ws, js-yaml, nodemailer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
111
dashcaddy-api/routes/events.js
Normal file
111
dashcaddy-api/routes/events.js
Normal file
@@ -0,0 +1,111 @@
|
||||
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
|
||||
* @returns {express.Router}
|
||||
*/
|
||||
module.exports = function({ resourceMonitor, healthChecker, updateManager, logError }) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
};
|
||||
Reference in New Issue
Block a user