const express = require('express'); module.exports = function(ctx) { const router = express.Router(); // ===== RESOURCE MONITORING ENDPOINTS ===== // Get all container stats (from resource monitor module) router.get('/monitoring/stats', ctx.asyncHandler(async (req, res) => { const stats = ctx.resourceMonitor.getAllStats(); res.json({ success: true, stats }); }, 'monitoring-stats')); // Get stats for specific container router.get('/monitoring/stats/:containerId', ctx.asyncHandler(async (req, res) => { const stats = ctx.resourceMonitor.getCurrentStats(req.params.containerId); if (!stats) { const { NotFoundError } = require('../errors'); throw new NotFoundError('Container'); } res.json({ success: true, stats }); }, 'monitoring-stats-container')); // Get historical stats router.get('/monitoring/history/:containerId', ctx.asyncHandler(async (req, res) => { const hours = parseInt(req.query.hours) || 24; const history = ctx.resourceMonitor.getHistoricalStats(req.params.containerId, hours); res.json({ success: true, history, hours }); }, 'monitoring-history')); // Get aggregated stats router.get('/monitoring/aggregated/:containerId', ctx.asyncHandler(async (req, res) => { const hours = parseInt(req.query.hours) || 24; const aggregated = ctx.resourceMonitor.getAggregatedStats(req.params.containerId, hours); if (!aggregated) { const { NotFoundError } = require('../errors'); throw new NotFoundError('Monitoring data'); } res.json({ success: true, aggregated, hours }); }, 'monitoring-aggregated')); // Configure alerts router.post('/monitoring/alerts/:containerId', ctx.asyncHandler(async (req, res) => { ctx.resourceMonitor.setAlertConfig(req.params.containerId, req.body); res.json({ success: true, message: 'Alert configuration saved' }); }, 'monitoring-alerts-set')); // Get alert configuration router.get('/monitoring/alerts/:containerId', ctx.asyncHandler(async (req, res) => { const config = ctx.resourceMonitor.getAlertConfig(req.params.containerId); res.json({ success: true, config: config || {} }); }, 'monitoring-alerts-get')); // Delete alert configuration router.delete('/monitoring/alerts/:containerId', ctx.asyncHandler(async (req, res) => { ctx.resourceMonitor.removeAlertConfig(req.params.containerId); res.json({ success: true, message: 'Alert configuration removed' }); }, 'monitoring-alerts-delete')); // ===== CONTAINER STATS ENDPOINTS (legacy /stats/) ===== // Get all container stats (live Docker stats) router.get('/stats/containers', ctx.asyncHandler(async (req, res) => { const containers = await ctx.docker.client.listContainers({ all: false }); const stats = []; for (const containerInfo of containers) { try { const container = ctx.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 console.log(`Could not get stats for ${containerInfo.Names[0]}:`, e.message); } } res.json({ success: true, stats, timestamp: new Date().toISOString() }); }, 'stats-containers')); // Get single container stats router.get('/stats/container/:id', ctx.asyncHandler(async (req, res) => { const container = ctx.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; } } res.json({ success: true, 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; };