require('dotenv').config(); const express = require('express'); const cors = require('cors'); const path = require('path'); const db = require('./db'); const prometheusService = require('./prometheus-service'); const app = express(); const PORT = process.env.PORT || 3000; app.use(cors()); app.use(express.json()); app.use(express.static(path.join(__dirname, '..', 'public'))); // ==================== Prometheus Source CRUD ==================== // Get all Prometheus sources app.get('/api/sources', async (req, res) => { try { const [rows] = await db.query('SELECT * FROM prometheus_sources ORDER BY created_at DESC'); // Test connectivity for each source const sourcesWithStatus = await Promise.all(rows.map(async (source) => { try { const response = await prometheusService.testConnection(source.url); return { ...source, status: 'online', version: response }; } catch (e) { return { ...source, status: 'offline', version: null }; } })); res.json(sourcesWithStatus); } catch (err) { console.error('Error fetching sources:', err); res.status(500).json({ error: 'Failed to fetch sources' }); } }); // Add a new Prometheus source app.post('/api/sources', async (req, res) => { const { name, url, description } = req.body; if (!name || !url) { return res.status(400).json({ error: 'Name and URL are required' }); } try { const [result] = await db.query( 'INSERT INTO prometheus_sources (name, url, description) VALUES (?, ?, ?)', [name, url, description || ''] ); const [rows] = await db.query('SELECT * FROM prometheus_sources WHERE id = ?', [result.insertId]); res.status(201).json(rows[0]); } catch (err) { console.error('Error adding source:', err); res.status(500).json({ error: 'Failed to add source' }); } }); // Update a Prometheus source app.put('/api/sources/:id', async (req, res) => { const { name, url, description } = req.body; try { await db.query( 'UPDATE prometheus_sources SET name = ?, url = ?, description = ? WHERE id = ?', [name, url, description || '', req.params.id] ); const [rows] = await db.query('SELECT * FROM prometheus_sources WHERE id = ?', [req.params.id]); res.json(rows[0]); } catch (err) { console.error('Error updating source:', err); res.status(500).json({ error: 'Failed to update source' }); } }); // Delete a Prometheus source app.delete('/api/sources/:id', async (req, res) => { try { await db.query('DELETE FROM prometheus_sources WHERE id = ?', [req.params.id]); res.json({ message: 'Source deleted' }); } catch (err) { console.error('Error deleting source:', err); res.status(500).json({ error: 'Failed to delete source' }); } }); // Test connection to a Prometheus source app.post('/api/sources/test', async (req, res) => { const { url } = req.body; try { const version = await prometheusService.testConnection(url); res.json({ status: 'ok', version }); } catch (err) { res.status(400).json({ status: 'error', message: err.message }); } }); // ==================== Metrics Aggregation ==================== // Get all aggregated metrics from all Prometheus sources app.get('/api/metrics/overview', async (req, res) => { try { const [sources] = await db.query('SELECT * FROM prometheus_sources'); if (sources.length === 0) { return res.json({ totalServers: 0, cpu: { used: 0, total: 0, percent: 0 }, memory: { used: 0, total: 0, percent: 0 }, disk: { used: 0, total: 0, percent: 0 }, network: { totalBandwidth: 0, rx: 0, tx: 0 }, traffic24h: { rx: 0, tx: 0, total: 0 }, servers: [] }); } const allMetrics = await Promise.all(sources.map(source => prometheusService.getOverviewMetrics(source.url, source.name).catch(err => { console.error(`Error fetching metrics from ${source.name}:`, err.message); return null; }) )); const validMetrics = allMetrics.filter(m => m !== null); // Aggregate across all sources let totalServers = 0; let cpuUsed = 0, cpuTotal = 0; let memUsed = 0, memTotal = 0; let diskUsed = 0, diskTotal = 0; let netRx = 0, netTx = 0; let traffic24hRx = 0, traffic24hTx = 0; let allServers = []; for (const m of validMetrics) { totalServers += m.totalServers; cpuUsed += m.cpu.used; cpuTotal += m.cpu.total; memUsed += m.memory.used; memTotal += m.memory.total; diskUsed += m.disk.used; diskTotal += m.disk.total; netRx += m.network.rx; netTx += m.network.tx; traffic24hRx += m.traffic24h.rx; traffic24hTx += m.traffic24h.tx; allServers = allServers.concat(m.servers); } res.json({ totalServers, cpu: { used: cpuUsed, total: cpuTotal, percent: cpuTotal > 0 ? (cpuUsed / cpuTotal * 100) : 0 }, memory: { used: memUsed, total: memTotal, percent: memTotal > 0 ? (memUsed / memTotal * 100) : 0 }, disk: { used: diskUsed, total: diskTotal, percent: diskTotal > 0 ? (diskUsed / diskTotal * 100) : 0 }, network: { totalBandwidth: netRx + netTx, rx: netRx, tx: netTx }, traffic24h: { rx: traffic24hRx, tx: traffic24hTx, total: traffic24hRx + traffic24hTx }, servers: allServers }); } catch (err) { console.error('Error fetching overview metrics:', err); res.status(500).json({ error: 'Failed to fetch metrics' }); } }); // Get network traffic history (past 24h in intervals) app.get('/api/metrics/network-history', async (req, res) => { try { const [sources] = await db.query('SELECT * FROM prometheus_sources'); if (sources.length === 0) { return res.json({ timestamps: [], rx: [], tx: [] }); } const allHistories = await Promise.all(sources.map(source => prometheusService.getNetworkHistory(source.url).catch(err => { console.error(`Error fetching network history from ${source.name}:`, err.message); return null; }) )); const validHistories = allHistories.filter(h => h !== null); if (validHistories.length === 0) { return res.json({ timestamps: [], rx: [], tx: [] }); } // Merge all histories by timestamp const merged = prometheusService.mergeNetworkHistories(validHistories); res.json(merged); } catch (err) { console.error('Error fetching network history:', err); res.status(500).json({ error: 'Failed to fetch network history' }); } }); // Get CPU usage history for sparklines app.get('/api/metrics/cpu-history', async (req, res) => { try { const [sources] = await db.query('SELECT * FROM prometheus_sources'); if (sources.length === 0) { return res.json({ timestamps: [], values: [] }); } const allHistories = await Promise.all(sources.map(source => prometheusService.getCpuHistory(source.url).catch(err => { console.error(`Error fetching CPU history from ${source.name}:`, err.message); return null; }) )); const validHistories = allHistories.filter(h => h !== null); if (validHistories.length === 0) { return res.json({ timestamps: [], values: [] }); } const merged = prometheusService.mergeCpuHistories(validHistories); res.json(merged); } catch (err) { console.error('Error fetching CPU history:', err); res.status(500).json({ error: 'Failed to fetch CPU history' }); } }); // SPA fallback app.get('*', (req, res) => { res.sendFile(path.join(__dirname, '..', 'public', 'index.html')); }); app.listen(PORT, () => { console.log(`\n 🚀 Data Visualization Display Wall`); console.log(` 📊 Server running at http://localhost:${PORT}`); console.log(` ⚙️ Configure Prometheus sources at http://localhost:${PORT}/settings\n`); });