const express = require('express'); const { success } = require('../response-helpers'); /** * Monitoring routes factory * @param {Object} deps - Explicit dependencies * @param {Object} deps.resourceMonitor - Resource monitoring manager * @param {Object} deps.docker - Docker client wrapper * @param {Function} deps.asyncHandler - Async route handler wrapper * @param {Object} deps.log - Logger instance * @returns {express.Router} */ module.exports = function({ resourceMonitor, docker, asyncHandler, log }) { const router = express.Router(); // ===== RESOURCE MONITORING ENDPOINTS ===== // Get all container stats (from resource monitor module) router.get('/monitoring/stats', asyncHandler(async (req, res) => { const stats = resourceMonitor.getAllStats(); success(res, { stats }); }, 'monitoring-stats')); // Get stats for specific container router.get('/monitoring/stats/:containerId', asyncHandler(async (req, res) => { const stats = resourceMonitor.getCurrentStats(req.params.containerId); if (!stats) { const { NotFoundError } = require('../errors'); throw new NotFoundError('Container'); } success(res, { stats }); }, 'monitoring-stats-container')); // Get historical stats router.get('/monitoring/history/:containerId', asyncHandler(async (req, res) => { const hours = parseInt(req.query.hours) || 24; const history = resourceMonitor.getHistoricalStats(req.params.containerId, hours); success(res, { history, hours }); }, 'monitoring-history')); // Get aggregated stats router.get('/monitoring/aggregated/:containerId', asyncHandler(async (req, res) => { const hours = parseInt(req.query.hours) || 24; const aggregated = resourceMonitor.getAggregatedStats(req.params.containerId, hours); if (!aggregated) { const { NotFoundError } = require('../errors'); throw new NotFoundError('Monitoring data'); } success(res, { aggregated, hours }); }, 'monitoring-aggregated')); // Configure alerts router.post('/monitoring/alerts/:containerId', asyncHandler(async (req, res) => { resourceMonitor.setAlertConfig(req.params.containerId, req.body); success(res, { message: 'Alert configuration saved' }); }, 'monitoring-alerts-set')); // Get alert configuration router.get('/monitoring/alerts/:containerId', asyncHandler(async (req, res) => { const config = resourceMonitor.getAlertConfig(req.params.containerId); success(res, { config: config || {} }); }, 'monitoring-alerts-get')); // Delete alert configuration router.delete('/monitoring/alerts/:containerId', asyncHandler(async (req, res) => { resourceMonitor.removeAlertConfig(req.params.containerId); success(res, { message: 'Alert configuration removed' }); }, 'monitoring-alerts-delete')); // ===== CONTAINER STATS ENDPOINTS (legacy /stats/) ===== // Get all container stats (live Docker stats) router.get('/stats/containers', asyncHandler(async (req, res) => { const containers = await docker.client.listContainers({ all: false }); const stats = []; for (const containerInfo of containers) { try { const container = docker.client.getContainer(containerInfo.Id); const containerStats = await container.stats({ stream: false }); // Calculate CPU percentage const cpuDelta = containerStats.cpu_stats.cpu_usage.total_usage - (containerStats.precpu_stats.cpu_usage?.total_usage || 0); const systemDelta = containerStats.cpu_stats.system_cpu_usage - (containerStats.precpu_stats.system_cpu_usage || 0); const cpuPercent = systemDelta > 0 ? (cpuDelta / systemDelta) * 100 * (containerStats.cpu_stats.online_cpus || 1) : 0; // Calculate memory usage const memUsage = containerStats.memory_stats.usage || 0; const memLimit = containerStats.memory_stats.limit || 1; const memPercent = (memUsage / memLimit) * 100; // Network stats let netRx = 0, netTx = 0; if (containerStats.networks) { for (const net of Object.values(containerStats.networks)) { netRx += net.rx_bytes || 0; netTx += net.tx_bytes || 0; } } stats.push({ id: containerInfo.Id.slice(0, 12), name: containerInfo.Names[0]?.replace(/^\//, '') || 'unknown', image: containerInfo.Image, status: containerInfo.State, cpu: { percent: Math.round(cpuPercent * 100) / 100 }, memory: { used: memUsage, limit: memLimit, percent: Math.round(memPercent * 100) / 100 }, network: { rx: netRx, tx: netTx } }); } catch (e) { // Skip containers we can't get stats for log.warn('monitoring', `Could not get stats for ${containerInfo.Names[0]}`, { error: e.message }); } } success(res, { stats, timestamp: new Date().toISOString() }); }, 'stats-containers')); // Get single container stats router.get('/stats/container/:id', asyncHandler(async (req, res) => { const container = docker.client.getContainer(req.params.id); const containerStats = await container.stats({ stream: false }); const info = await container.inspect(); // Calculate CPU percentage const cpuDelta = containerStats.cpu_stats.cpu_usage.total_usage - (containerStats.precpu_stats.cpu_usage?.total_usage || 0); const systemDelta = containerStats.cpu_stats.system_cpu_usage - (containerStats.precpu_stats.system_cpu_usage || 0); const cpuPercent = systemDelta > 0 ? (cpuDelta / systemDelta) * 100 * (containerStats.cpu_stats.online_cpus || 1) : 0; // Memory const memUsage = containerStats.memory_stats.usage || 0; const memLimit = containerStats.memory_stats.limit || 1; // Network let netRx = 0, netTx = 0; if (containerStats.networks) { for (const net of Object.values(containerStats.networks)) { netRx += net.rx_bytes || 0; netTx += net.tx_bytes || 0; } } success(res, { stats: { name: info.Name.replace(/^\//, ''), image: info.Config.Image, status: info.State.Status, started: info.State.StartedAt, cpu: { percent: Math.round(cpuPercent * 100) / 100 }, memory: { used: memUsage, limit: memLimit, percent: Math.round((memUsage / memLimit) * 100 * 100) / 100 }, network: { rx: netRx, tx: netTx } } }); }, 'stats-container')); return router; };