Files
PromdataPanel/server/index.js
CN-JS-HuiBai e69424dab2 First Commit
2026-04-04 15:13:32 +08:00

247 lines
7.8 KiB
JavaScript

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`);
});