feat: homepage SERVER METRICS now live — real CPU/MEM/DISK/BW/network/uptime/health/containers
This commit is contained in:
parent
b03bba89f7
commit
f90f81d44a
1 changed files with 118 additions and 64 deletions
182
js/main.js
182
js/main.js
|
|
@ -591,11 +591,13 @@
|
||||||
const ulData = [];
|
const ulData = [];
|
||||||
const maxPoints = 80;
|
const maxPoints = 80;
|
||||||
|
|
||||||
// Seed initial data
|
// Seed with zeros — graph populates with real samples from initLiveStats
|
||||||
for (let i = 0; i < maxPoints; i++) {
|
for (let i = 0; i < maxPoints; i++) {
|
||||||
dlData.push(30 + Math.random() * 40);
|
dlData.push(0);
|
||||||
ulData.push(20 + Math.random() * 30);
|
ulData.push(0);
|
||||||
}
|
}
|
||||||
|
// Auto-scaling peak (starts at 10 Mbps floor)
|
||||||
|
let peakMbps = 10;
|
||||||
|
|
||||||
function drawLine(data, color, alpha) {
|
function drawLine(data, color, alpha) {
|
||||||
const w = canvas.width;
|
const w = canvas.width;
|
||||||
|
|
@ -609,7 +611,8 @@
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
const x = i * step;
|
const x = i * step;
|
||||||
const y = h - (data[i] / 100) * h;
|
const scaled = Math.min(100, (data[i] / peakMbps) * 100);
|
||||||
|
const y = h - (scaled / 100) * h;
|
||||||
if (i === 0) ctx.moveTo(x, y);
|
if (i === 0) ctx.moveTo(x, y);
|
||||||
else ctx.lineTo(x, y);
|
else ctx.lineTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
@ -647,32 +650,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStats() {
|
// Readouts (dlSpeed/ulSpeed/packetCount) are updated by initLiveStats with real values
|
||||||
const dl = dlData[dlData.length - 1];
|
|
||||||
const ul = ulData[ulData.length - 1];
|
|
||||||
const dlEl = document.getElementById('dlSpeed');
|
|
||||||
const ulEl = document.getElementById('ulSpeed');
|
|
||||||
const pkEl = document.getElementById('packetCount');
|
|
||||||
if (dlEl) dlEl.textContent = (dl * 0.032).toFixed(1) + ' Gbps';
|
|
||||||
if (ulEl) ulEl.textContent = (ul * 0.028).toFixed(1) + ' Gbps';
|
|
||||||
if (pkEl) pkEl.textContent = Math.floor(dl * 180).toLocaleString() + ' pkt/s';
|
|
||||||
}
|
|
||||||
|
|
||||||
function animate() {
|
function animate() {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
drawGrid();
|
drawGrid();
|
||||||
|
|
||||||
// Push new data
|
// Pull latest real Mbps from shared state set by initLiveStats
|
||||||
const lastDl = dlData[dlData.length - 1];
|
const stats = window.__serverStats || { dlMbps: 0, ulMbps: 0 };
|
||||||
const lastUl = ulData[ulData.length - 1];
|
dlData.push(stats.dlMbps || 0);
|
||||||
dlData.push(Math.max(10, Math.min(95, lastDl + (Math.random() - 0.48) * 12)));
|
ulData.push(stats.ulMbps || 0);
|
||||||
ulData.push(Math.max(5, Math.min(85, lastUl + (Math.random() - 0.48) * 10)));
|
|
||||||
if (dlData.length > maxPoints) dlData.shift();
|
if (dlData.length > maxPoints) dlData.shift();
|
||||||
if (ulData.length > maxPoints) ulData.shift();
|
if (ulData.length > maxPoints) ulData.shift();
|
||||||
|
|
||||||
drawLine(dlData, '#333333', 0.9);
|
// Smooth auto-scaling peak
|
||||||
drawLine(ulData, '#00a8ff', 0.6);
|
const curMax = Math.max.apply(null, dlData.concat(ulData).concat([1]));
|
||||||
updateStats();
|
peakMbps = peakMbps * 0.95 + Math.max(10, curMax * 1.2) * 0.05;
|
||||||
|
|
||||||
|
drawLine(dlData, '#00ff9d', 0.9); // download = green
|
||||||
|
drawLine(ulData, '#00a8ff', 0.7); // upload = blue
|
||||||
|
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
}
|
}
|
||||||
|
|
@ -818,67 +814,125 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── LIVE SERVER STATS (from API) ───
|
// ─── LIVE SERVER STATS (from API) — REAL DATA ───
|
||||||
|
// Shared state used by the network graph
|
||||||
|
window.__serverStats = {
|
||||||
|
lastRx: null, lastTx: null, lastT: null,
|
||||||
|
dlMbps: 0, ulMbps: 0, bwPct: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatUptime(sec) {
|
||||||
|
sec = Math.floor(sec);
|
||||||
|
const d = Math.floor(sec / 86400);
|
||||||
|
const h = Math.floor((sec % 86400) / 3600);
|
||||||
|
const m = Math.floor((sec % 3600) / 60);
|
||||||
|
return d + 'd ' + String(h).padStart(2, '0') + 'h ' + String(m).padStart(2, '0') + 'm';
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyBar(barId, valId, value, unit) {
|
||||||
|
const barEl = document.getElementById(barId);
|
||||||
|
const valEl = document.getElementById(valId);
|
||||||
|
if (!barEl || !valEl) return;
|
||||||
|
const v = Math.max(0, Math.min(100, value));
|
||||||
|
barEl.style.width = v + '%';
|
||||||
|
valEl.textContent = Math.round(v) + (unit || '%');
|
||||||
|
if (v > 85) {
|
||||||
|
barEl.style.background = 'linear-gradient(90deg, #ff2d2d, rgba(255,71,87,0.4))';
|
||||||
|
barEl.style.boxShadow = '0 0 8px rgba(255,71,87,0.4)';
|
||||||
|
valEl.style.color = '#ff2d2d';
|
||||||
|
} else if (v > 70) {
|
||||||
|
barEl.style.background = 'linear-gradient(90deg, #c9a227, rgba(255,165,2,0.4))';
|
||||||
|
barEl.style.boxShadow = '0 0 8px rgba(255,165,2,0.3)';
|
||||||
|
valEl.style.color = '#c9a227';
|
||||||
|
} else {
|
||||||
|
barEl.style.background = '';
|
||||||
|
barEl.style.boxShadow = '';
|
||||||
|
valEl.style.color = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initLiveStats() {
|
function initLiveStats() {
|
||||||
|
// Assumed max link capacity for bandwidth % scaling (1 Gbps)
|
||||||
|
const LINK_MBPS = 1000;
|
||||||
|
|
||||||
function fetchStats() {
|
function fetchStats() {
|
||||||
fetch(API_BASE + '/stats')
|
fetch(API_BASE + '/stats')
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(d => {
|
.then(d => {
|
||||||
// Update metric bars with real data
|
// CPU / MEM / DISK bars
|
||||||
const metrics = [
|
applyBar('cpuBar', 'cpuVal', d.cpu_percent);
|
||||||
{ bar: 'cpuBar', val: 'cpuVal', value: d.cpu_percent },
|
applyBar('memBar', 'memVal', d.memory_percent);
|
||||||
{ bar: 'memBar', val: 'memVal', value: d.memory_percent },
|
applyBar('diskBar', 'diskVal', d.disk_percent);
|
||||||
{ bar: 'diskBar', val: 'diskVal', value: d.disk_percent },
|
|
||||||
];
|
|
||||||
metrics.forEach(m => {
|
|
||||||
const barEl = document.getElementById(m.bar);
|
|
||||||
const valEl = document.getElementById(m.val);
|
|
||||||
if (!barEl || !valEl) return;
|
|
||||||
barEl.style.width = m.value + '%';
|
|
||||||
valEl.textContent = Math.round(m.value) + '%';
|
|
||||||
if (m.value > 85) {
|
|
||||||
barEl.style.background = 'linear-gradient(90deg, #ff2d2d, rgba(255,71,87,0.4))';
|
|
||||||
barEl.style.boxShadow = '0 0 8px rgba(255,71,87,0.4)';
|
|
||||||
valEl.style.color = '#ff2d2d';
|
|
||||||
} else if (m.value > 70) {
|
|
||||||
barEl.style.background = 'linear-gradient(90deg, #c9a227, rgba(255,165,2,0.4))';
|
|
||||||
barEl.style.boxShadow = '0 0 8px rgba(255,165,2,0.3)';
|
|
||||||
valEl.style.color = '#c9a227';
|
|
||||||
} else {
|
|
||||||
barEl.style.background = '';
|
|
||||||
barEl.style.boxShadow = '';
|
|
||||||
valEl.style.color = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update uptime from real data
|
// Bandwidth — compute MB/s delta from network_rx_bytes + network_tx_bytes
|
||||||
const uptimeEl = document.getElementById('uptime');
|
const now = (d.timestamp || Date.now() / 1000);
|
||||||
|
const s = window.__serverStats;
|
||||||
|
if (s.lastRx !== null && s.lastT !== null && now > s.lastT) {
|
||||||
|
const dt = Math.max(0.1, now - s.lastT);
|
||||||
|
const rxBps = Math.max(0, (d.network_rx_bytes - s.lastRx) / dt);
|
||||||
|
const txBps = Math.max(0, (d.network_tx_bytes - s.lastTx) / dt);
|
||||||
|
const rxMbps = (rxBps * 8) / 1e6;
|
||||||
|
const txMbps = (txBps * 8) / 1e6;
|
||||||
|
s.dlMbps = rxMbps;
|
||||||
|
s.ulMbps = txMbps;
|
||||||
|
const totalMbps = rxMbps + txMbps;
|
||||||
|
s.bwPct = Math.min(100, (totalMbps / LINK_MBPS) * 100);
|
||||||
|
applyBar('bwBar', 'bwVal', s.bwPct);
|
||||||
|
|
||||||
|
// Network graph stat readouts
|
||||||
|
const dlEl = document.getElementById('dlSpeed');
|
||||||
|
const ulEl = document.getElementById('ulSpeed');
|
||||||
|
const pkEl = document.getElementById('packetCount');
|
||||||
|
const fmtSpeed = mbps => mbps >= 1000 ? (mbps / 1000).toFixed(2) + ' Gbps'
|
||||||
|
: mbps >= 1 ? mbps.toFixed(1) + ' Mbps'
|
||||||
|
: (mbps * 1000).toFixed(0) + ' Kbps';
|
||||||
|
if (dlEl) dlEl.textContent = fmtSpeed(rxMbps);
|
||||||
|
if (ulEl) ulEl.textContent = fmtSpeed(txMbps);
|
||||||
|
if (pkEl) pkEl.textContent = (d.active_connections || 0).toLocaleString() + ' conns';
|
||||||
|
} else {
|
||||||
|
// Seed on first sample
|
||||||
|
applyBar('bwBar', 'bwVal', 0);
|
||||||
|
}
|
||||||
|
s.lastRx = d.network_rx_bytes;
|
||||||
|
s.lastTx = d.network_tx_bytes;
|
||||||
|
s.lastT = now;
|
||||||
|
|
||||||
|
// Uptime (HTML id is serverUptime)
|
||||||
|
const uptimeEl = document.getElementById('serverUptime') || document.getElementById('uptime');
|
||||||
if (uptimeEl && d.uptime_seconds) {
|
if (uptimeEl && d.uptime_seconds) {
|
||||||
const totalSec = Math.floor(d.uptime_seconds);
|
uptimeEl.textContent = formatUptime(d.uptime_seconds);
|
||||||
const days = Math.floor(totalSec / 86400);
|
|
||||||
const hours = Math.floor((totalSec % 86400) / 3600);
|
|
||||||
const mins = Math.floor((totalSec % 3600) / 60);
|
|
||||||
uptimeEl.textContent = days + 'd ' + String(hours).padStart(2, '0') + 'h ' + String(mins).padStart(2, '0') + 'm';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update server health
|
// Server health — weighted score from cpu/mem/disk
|
||||||
const healthEl = document.getElementById('serverHealth');
|
const healthEl = document.getElementById('serverHealth');
|
||||||
if (healthEl) {
|
if (healthEl) {
|
||||||
const health = Math.round(100 - (d.cpu_percent * 0.3 + d.memory_percent * 0.3 + d.disk_percent * 0.4) / 3);
|
const penalty = (d.cpu_percent * 0.35 + d.memory_percent * 0.35 + d.disk_percent * 0.30);
|
||||||
healthEl.textContent = Math.min(99, Math.max(80, health)) + '%';
|
const health = Math.round(100 - penalty * 0.35);
|
||||||
|
healthEl.textContent = Math.min(99, Math.max(40, health)) + '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container counts
|
// Power panel — use inverse of CPU load as "reliability" %
|
||||||
|
const powerEl = document.getElementById('powerPct');
|
||||||
|
if (powerEl) {
|
||||||
|
const rel = Math.max(60, Math.min(100, 100 - d.cpu_percent * 0.4));
|
||||||
|
powerEl.innerHTML = Math.round(rel) + '<span class="power-unit">%</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Containers
|
||||||
const containerEl = document.getElementById('containerUp');
|
const containerEl = document.getElementById('containerUp');
|
||||||
const containerTotalEl = document.querySelector('.container-total');
|
const containerTotalEl = document.querySelector('.container-total');
|
||||||
|
const containerBar = document.getElementById('containerBar');
|
||||||
if (containerEl) containerEl.textContent = d.container_running;
|
if (containerEl) containerEl.textContent = d.container_running;
|
||||||
if (containerTotalEl) containerTotalEl.textContent = d.container_total;
|
if (containerTotalEl) containerTotalEl.textContent = d.container_total;
|
||||||
|
if (containerBar && d.container_total) {
|
||||||
|
containerBar.style.width = ((d.container_running / d.container_total) * 100) + '%';
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchStats();
|
fetchStats();
|
||||||
setInterval(fetchStats, 10000); // refresh every 10s
|
setInterval(fetchStats, 5000); // refresh every 5s
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── LIVE WEATHER ───
|
// ─── LIVE WEATHER ───
|
||||||
|
|
@ -940,10 +994,10 @@
|
||||||
initHUDFlickers();
|
initHUDFlickers();
|
||||||
initPanelGlow();
|
initPanelGlow();
|
||||||
initNetworkGraph();
|
initNetworkGraph();
|
||||||
initMetricBars();
|
// initMetricBars(); // disabled — real data from /api/stats via initLiveStats
|
||||||
initScanBar();
|
initScanBar();
|
||||||
initPowerFlicker();
|
// initPowerFlicker(); // disabled — real load_avg-based via initLiveStats
|
||||||
initServerHealth();
|
// initServerHealth(); // disabled — real health computed in initLiveStats
|
||||||
|
|
||||||
// Live API integrations
|
// Live API integrations
|
||||||
initBlogFeed();
|
initBlogFeed();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue