(function () { const metricsUrl = window.adminMetricsUrl; if (!metricsUrl) return; const cpuCanvas = document.getElementById("chart-cpu"); const memCanvas = document.getElementById("chart-mem"); if (!cpuCanvas || !memCanvas || typeof Chart === "undefined") return; const chartOptions = { responsive: true, maintainAspectRatio: false, animation: { duration: 300 }, scales: { x: { ticks: { maxTicksLimit: 6, color: "#9a9aad", font: { size: 10 } }, grid: { color: "rgba(255,255,255,0.06)" } }, y: { min: 0, max: 100, ticks: { color: "#9a9aad", font: { size: 10 }, callback: (v) => v + "%" }, grid: { color: "rgba(255,255,255,0.06)" } } }, plugins: { legend: { display: false } } }; function labelsFromHistory(history) { return history.map((p) => { const d = new Date(p.at); return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); }); } const initial = window.adminInitialHistory || []; const cpuChart = new Chart(cpuCanvas, { type: "line", data: { labels: labelsFromHistory(initial), datasets: [{ data: initial.map((p) => p.cpu), borderColor: "#e53935", backgroundColor: "rgba(229, 57, 53, 0.12)", fill: true, tension: 0.35, pointRadius: 0, borderWidth: 2 }] }, options: chartOptions }); const memChart = new Chart(memCanvas, { type: "line", data: { labels: labelsFromHistory(initial), datasets: [{ data: initial.map((p) => p.mem), borderColor: "#43a047", backgroundColor: "rgba(67, 160, 71, 0.12)", fill: true, tension: 0.35, pointRadius: 0, borderWidth: 2 }] }, options: chartOptions }); function updateCharts(history) { const labels = labelsFromHistory(history); cpuChart.data.labels = labels; cpuChart.data.datasets[0].data = history.map((p) => p.cpu); cpuChart.update("none"); memChart.data.labels = labels; memChart.data.datasets[0].data = history.map((p) => p.mem); memChart.update("none"); } function setText(id, value) { const el = document.getElementById(id); if (el) el.textContent = value; } function formatBytes(bytes) { if (bytes == null) return "—"; const units = ["B", "KB", "MB", "GB", "TB"]; let size = bytes; let i = 0; while (size >= 1024 && i < units.length - 1) { size /= 1024; i += 1; } return (size >= 10 ? size.toFixed(0) : size.toFixed(1)) + " " + units[i]; } async function poll() { try { const res = await fetch(metricsUrl, { headers: { Accept: "application/json" }, credentials: "same-origin" }); if (!res.ok) return; const data = await res.json(); const host = data.host || {}; const stats = data.stats || {}; setText("kpi-active-sessions", stats.active_sessions); setText("kpi-sessions-today", stats.sessions_today); setText("kpi-sessions-month", stats.sessions_month); setText("kpi-cpu", (host.cpu ?? 0) + "%"); setText("kpi-mem", (host.memory?.used_percent ?? 0) + "%"); if (host.network) setText("kpi-network", formatBytes(host.network.total_bytes)); if (host.history) updateCharts(host.history); } catch (_e) { /* ignore polling errors */ } } setInterval(poll, 8000); })();