feat: homepage SERVER METRICS now live — real CPU/MEM/DISK/BW/network/uptime/health/containers

This commit is contained in:
jae 2026-04-19 12:48:37 +00:00
parent b03bba89f7
commit f90f81d44a

View file

@ -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,29 +814,33 @@
}); });
} }
// ─── LIVE SERVER STATS (from API) ─── // ─── LIVE SERVER STATS (from API) — REAL DATA ───
function initLiveStats() { // Shared state used by the network graph
function fetchStats() { window.__serverStats = {
fetch(API_BASE + '/stats') lastRx: null, lastTx: null, lastT: null,
.then(r => r.json()) dlMbps: 0, ulMbps: 0, bwPct: 0
.then(d => { };
// Update metric bars with real data
const metrics = [ function formatUptime(sec) {
{ bar: 'cpuBar', val: 'cpuVal', value: d.cpu_percent }, sec = Math.floor(sec);
{ bar: 'memBar', val: 'memVal', value: d.memory_percent }, const d = Math.floor(sec / 86400);
{ bar: 'diskBar', val: 'diskVal', value: d.disk_percent }, const h = Math.floor((sec % 86400) / 3600);
]; const m = Math.floor((sec % 3600) / 60);
metrics.forEach(m => { return d + 'd ' + String(h).padStart(2, '0') + 'h ' + String(m).padStart(2, '0') + 'm';
const barEl = document.getElementById(m.bar); }
const valEl = document.getElementById(m.val);
function applyBar(barId, valId, value, unit) {
const barEl = document.getElementById(barId);
const valEl = document.getElementById(valId);
if (!barEl || !valEl) return; if (!barEl || !valEl) return;
barEl.style.width = m.value + '%'; const v = Math.max(0, Math.min(100, value));
valEl.textContent = Math.round(m.value) + '%'; barEl.style.width = v + '%';
if (m.value > 85) { valEl.textContent = Math.round(v) + (unit || '%');
if (v > 85) {
barEl.style.background = 'linear-gradient(90deg, #ff2d2d, rgba(255,71,87,0.4))'; 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)'; barEl.style.boxShadow = '0 0 8px rgba(255,71,87,0.4)';
valEl.style.color = '#ff2d2d'; valEl.style.color = '#ff2d2d';
} else if (m.value > 70) { } else if (v > 70) {
barEl.style.background = 'linear-gradient(90deg, #c9a227, rgba(255,165,2,0.4))'; 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)'; barEl.style.boxShadow = '0 0 8px rgba(255,165,2,0.3)';
valEl.style.color = '#c9a227'; valEl.style.color = '#c9a227';
@ -849,36 +849,90 @@
barEl.style.boxShadow = ''; barEl.style.boxShadow = '';
valEl.style.color = ''; valEl.style.color = '';
} }
});
// Update uptime from real data
const uptimeEl = document.getElementById('uptime');
if (uptimeEl && d.uptime_seconds) {
const totalSec = Math.floor(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 function initLiveStats() {
// Assumed max link capacity for bandwidth % scaling (1 Gbps)
const LINK_MBPS = 1000;
function fetchStats() {
fetch(API_BASE + '/stats')
.then(r => r.json())
.then(d => {
// CPU / MEM / DISK bars
applyBar('cpuBar', 'cpuVal', d.cpu_percent);
applyBar('memBar', 'memVal', d.memory_percent);
applyBar('diskBar', 'diskVal', d.disk_percent);
// Bandwidth — compute MB/s delta from network_rx_bytes + network_tx_bytes
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) {
uptimeEl.textContent = formatUptime(d.uptime_seconds);
}
// 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();