- monitoring.js: Added log dependency, replaced console.log with log.warn - themes.js: Added log dependency, replaced console.error with log.error - src/app.js: Pass log to monitoringRoutes and themesRoutes This fixes error messages being lost to stdout instead of proper log files.
177 lines
6.5 KiB
JavaScript
177 lines
6.5 KiB
JavaScript
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;
|
|
};
|