"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.observabilityRouter = void 0; const express_1 = require("express"); const auth_middleware_1 = require("../../middleware/auth.middleware"); const rbac_middleware_1 = require("../../middleware/rbac.middleware"); const rate_limit_1 = require("../../middleware/rate-limit"); const env_1 = require("../../config/env"); const logger_1 = require("../../utils/logger"); const metrics_1 = require("../../utils/metrics"); const fetch_with_timeout_1 = require("../../utils/fetch-with-timeout"); const promql_validator_1 = require("../../utils/promql-validator"); const health_check_1 = require("../../utils/health-check"); const router = (0, express_1.Router)(); router.use(auth_middleware_1.authenticate); router.use((0, rbac_middleware_1.requireRole)('SUPER_ADMIN')); // Stricter rate limit for observability endpoints (info disclosure risk) router.use(rate_limit_1.observabilityRateLimit); // Removed duplicated isServiceOnline - now using shared utility from utils/health-check.ts // GET /api/observability/status — check health of all 7 monitoring services router.get('/status', async (_req, res, next) => { try { const [prometheusOnline, grafanaOnline, alertmanagerOnline, cadvisorOnline, nodeExporterOnline, redisExporterOnline, gotifyOnline,] = await Promise.all([ (0, health_check_1.isServiceOnline)(`${env_1.env.PROMETHEUS_URL}/api/v1/status/config`), (0, health_check_1.isServiceOnline)(`${env_1.env.GRAFANA_URL}/api/health`), (0, health_check_1.isServiceOnline)(`${env_1.env.ALERTMANAGER_URL}/api/v2/status`), (0, health_check_1.isServiceOnline)(env_1.env.CADVISOR_URL), (0, health_check_1.isServiceOnline)(`${env_1.env.NODE_EXPORTER_URL}/metrics`), (0, health_check_1.isServiceOnline)(`${env_1.env.REDIS_EXPORTER_URL}/metrics`), (0, health_check_1.isServiceOnline)(`${env_1.env.GOTIFY_URL}/health`), ]); // Update Prometheus gauges (0, metrics_1.setServiceUp)('prometheus', prometheusOnline); (0, metrics_1.setServiceUp)('grafana', grafanaOnline); (0, metrics_1.setServiceUp)('alertmanager', alertmanagerOnline); (0, metrics_1.setServiceUp)('cadvisor', cadvisorOnline); (0, metrics_1.setServiceUp)('node_exporter', nodeExporterOnline); (0, metrics_1.setServiceUp)('redis_exporter', redisExporterOnline); (0, metrics_1.setServiceUp)('gotify', gotifyOnline); res.json({ prometheus: { online: prometheusOnline, url: `http://localhost:${env_1.env.PROMETHEUS_PORT}` }, grafana: { online: grafanaOnline, url: `http://localhost:${env_1.env.GRAFANA_PORT}` }, alertmanager: { online: alertmanagerOnline, url: `http://localhost:${env_1.env.ALERTMANAGER_PORT}` }, cadvisor: { online: cadvisorOnline, url: `http://localhost:${env_1.env.CADVISOR_PORT}` }, nodeExporter: { online: nodeExporterOnline, url: `http://localhost:${env_1.env.NODE_EXPORTER_PORT}` }, redisExporter: { online: redisExporterOnline, url: `http://localhost:${env_1.env.REDIS_EXPORTER_PORT}` }, gotify: { online: gotifyOnline, url: `http://localhost:${env_1.env.GOTIFY_PORT}` }, }); } catch (err) { logger_1.logger.error('Failed to check observability services status', err); next(err); } }); // GET /api/observability/metrics-summary — fetch key metrics from Prometheus router.get('/metrics-summary', async (_req, res, next) => { try { // Query Prometheus for key metrics const prometheusOnline = await (0, health_check_1.isServiceOnline)(`${env_1.env.PROMETHEUS_URL}/api/v1/status/config`); if (!prometheusOnline) { return res.json({ apiUptime: { value: 0, unit: 's' }, emailQueueSize: { value: 0, unit: 'jobs' }, requestRate: { value: 0, unit: 'req/s' }, emailErrorRate: { value: 0, unit: '%' }, activeSessions: { value: 0, unit: 'sessions' }, activeCanvassSessions: { value: 0, unit: 'sessions' }, redisHealth: { value: 0, unit: 'status' }, }); } // Define and validate PromQL queries const promqlQueries = [ 'time()-process_start_time_seconds', 'cm_email_queue_size', 'rate(http_request_total[5m])', 'rate(cm_email_send_errors_total[5m])*100', 'cm_active_sessions', 'cm_active_canvass_sessions', 'redis_up', ]; // Validate all queries before executing (prevents injection) (0, promql_validator_1.validatePromQLQueries)(promqlQueries); // Fetch metrics in parallel (with 5s timeout per query) const queries = promqlQueries.map(query => (0, fetch_with_timeout_1.fetchWithTimeout)(`${env_1.env.PROMETHEUS_URL}/api/v1/query?query=${encodeURIComponent(query)}`, {}, 5000).then(r => r.json())); const [uptimeRes, queueSizeRes, requestRateRes, errorRateRes, sessionsRes, canvassSessionsRes, redisHealthRes,] = (await Promise.all(queries)); res.json({ apiUptime: { value: parseFloat(uptimeRes?.data?.result?.[0]?.value?.[1] || '0'), unit: 's', }, emailQueueSize: { value: parseFloat(queueSizeRes?.data?.result?.[0]?.value?.[1] || '0'), unit: 'jobs', }, requestRate: { value: parseFloat(requestRateRes?.data?.result?.[0]?.value?.[1] || '0'), unit: 'req/s', }, emailErrorRate: { value: parseFloat(errorRateRes?.data?.result?.[0]?.value?.[1] || '0'), unit: '%', }, activeSessions: { value: parseFloat(sessionsRes?.data?.result?.[0]?.value?.[1] || '0'), unit: 'sessions', }, activeCanvassSessions: { value: parseFloat(canvassSessionsRes?.data?.result?.[0]?.value?.[1] || '0'), unit: 'sessions', }, redisHealth: { value: parseFloat(redisHealthRes?.data?.result?.[0]?.value?.[1] || '0'), unit: 'status', }, }); } catch (err) { logger_1.logger.error('Failed to fetch metrics summary', err); // Return default values on error res.json({ apiUptime: { value: 0, unit: 's' }, emailQueueSize: { value: 0, unit: 'jobs' }, requestRate: { value: 0, unit: 'req/s' }, emailErrorRate: { value: 0, unit: '%' }, activeSessions: { value: 0, unit: 'sessions' }, activeCanvassSessions: { value: 0, unit: 'sessions' }, redisHealth: { value: 0, unit: 'status' }, }); } }); // GET /api/observability/alerts — fetch active alerts from Alertmanager router.get('/alerts', async (_req, res, next) => { try { const alertmanagerOnline = await (0, health_check_1.isServiceOnline)(`${env_1.env.ALERTMANAGER_URL}/api/v2/status`); if (!alertmanagerOnline) { return res.json({ total: 0, critical: 0, warning: 0, info: 0, alerts: [], }); } const response = await (0, fetch_with_timeout_1.fetchWithTimeout)(`${env_1.env.ALERTMANAGER_URL}/api/v2/alerts?active=true`, {}, 3000); const alerts = (await response.json()); // Parse alerts by severity let critical = 0; let warning = 0; let info = 0; const parsedAlerts = (alerts || []).map((alert) => { const severity = alert.labels?.severity || 'info'; if (severity === 'critical') critical++; else if (severity === 'warning') warning++; else info++; return { id: alert.fingerprint || Math.random().toString(), severity: severity, name: alert.labels?.alertname || 'Unknown', summary: alert.annotations?.summary || alert.annotations?.description || 'No description', description: alert.annotations?.description, startsAt: alert.startsAt, }; }); res.json({ total: parsedAlerts.length, critical, warning, info, alerts: parsedAlerts, }); } catch (err) { logger_1.logger.error('Failed to fetch alerts', err); res.json({ total: 0, critical: 0, warning: 0, info: 0, alerts: [], }); } }); exports.observabilityRouter = router; //# sourceMappingURL=observability.routes.js.map