feat(api): add cached container cpu/mem fields on containers-dashboard

This commit is contained in:
kodi
2026-02-25 14:42:03 +01:00
parent 658e41cfba
commit 8e4e0067ff
2 changed files with 51 additions and 46 deletions
+50 -46
View File
@@ -501,9 +501,9 @@
if (tab === "images") {
loadImages();
}
// Start/stop live stats alleen in Containers tab
if (tab === 'containers') startContainersStatsStream();
else stopContainersStatsStream();
// Start/stop live stats alleen in Containers tab (polling via /containers-dashboard)
if (tab === 'containers') startContainersDashboardStatsPoll();
else stopContainersDashboardStatsPoll();
refreshActive();
}
@@ -526,9 +526,9 @@
document.addEventListener("visibilitychange", () => {
if (document.visibilityState !== "visible") {
stopContainersStatsStream();
stopContainersDashboardStatsPoll();
} else if (currentTab === "containers") {
startContainersStatsStream();
startContainersDashboardStatsPoll();
}
});
@@ -814,58 +814,69 @@
renderContainersGrouped(list, tbody, podStatus);
}
let containersStatsES = null;
let containersDashboardStatsTimer = null;
let containersDashboardStatsInFlight = false;
function startContainersDashboardStatsPoll() {
if (containersDashboardStatsTimer) return;
// immediate tick, daarna elke 1s
pollContainersDashboardStatsOnce();
containersDashboardStatsTimer = setInterval(pollContainersDashboardStatsOnce, 1000);
}
function startContainersStatsStream() {
if (containersStatsES) return;
function stopContainersDashboardStatsPoll() {
if (!containersDashboardStatsTimer) return;
clearInterval(containersDashboardStatsTimer);
containersDashboardStatsTimer = null;
resetPodTotals();
}
containersStatsES = new EventSource("/api/containers/stats/stream?interval=1");
async function pollContainersDashboardStatsOnce() {
if (containersDashboardStatsInFlight) return;
containersDashboardStatsInFlight = true;
try {
const containers = await api('/containers-dashboard', 'GET');
const list = Array.isArray(containers) ? containers : (containers?.containers || []);
containersStatsES.addEventListener("stats", (ev) => {
let payload;
try {
payload = JSON.parse(ev.data);
} catch (e) {
// Slechte SSE chunk -> negeren zodat de stream niet "stilvalt"
return;
}
const statsList = payload?.data?.Stats || [];
// totals per pod voor deze SSE tick
// totals per pod voor deze poll tick
const podCpu = new Map(); // podName -> cpuPct sum
const podMem = new Map(); // podName -> memBytes sum
const podMemPct = new Map(); // podName -> memPct sum
for (const st of statsList) {
const cname = normalizeContainerName(st?.Name);
for (const c of (list || [])) {
const cname = normalizeContainerName((c?.Names && c.Names[0]) ? c.Names[0] : (c?.Names || c?.Name || c?.name || ''));
if (!cname) continue;
const key = cssSafeId(cname);
const cpuPct = Number(st?.CPUPerc ?? st?.CPU ?? st?.AvgCPU ?? 0);
const memBytes = Number(st?.MemUsage ?? 0);
const memPct = Number(st?.MemPerc ?? 0);
const cpuRaw = c?._dashboard_cpu;
const memBytesRaw = c?._dashboard_mem_usage;
const memPctRaw = c?._dashboard_mem_perc;
const cpuPct = Number(cpuRaw);
const memBytes = Number(memBytesRaw);
const memPct = Number(memPctRaw);
const pod = containersC2P.get(cname);
if (pod) {
podCpu.set(pod, (podCpu.get(pod) || 0) + cpuPct);
podMem.set(pod, (podMem.get(pod) || 0) + memBytes);
podMemPct.set(pod, (podMemPct.get(pod) || 0) + memPct);
if (Number.isFinite(cpuPct)) podCpu.set(pod, (podCpu.get(pod) || 0) + cpuPct);
if (Number.isFinite(memBytes)) podMem.set(pod, (podMem.get(pod) || 0) + memBytes);
if (Number.isFinite(memPct)) podMemPct.set(pod, (podMemPct.get(pod) || 0) + memPct);
}
// CPU: jouw API geeft CPU als fractie (0.00384 -> 0.384%)
const cpuEl = document.getElementById(`cpu-${key}`);
if (cpuEl) cpuEl.textContent = cpuPct.toFixed(2) + "%";
if (cpuEl) cpuEl.textContent = Number.isFinite(cpuPct) ? (cpuPct.toFixed(2) + "%") : "-";
// MEM: bytes + percentage
const memEl = document.getElementById(`mem-${key}`);
if (memEl) {
const mem = formatBytes(memBytes);
memEl.textContent = `${mem} (${memPct.toFixed(1)}%)`;
if (Number.isFinite(memBytes) && Number.isFinite(memPct)) {
memEl.textContent = `${formatBytes(memBytes)} (${memPct.toFixed(1)}%)`;
} else {
memEl.textContent = "-";
}
}
}
// NA de container-loop: pod totals renderen
for (const [pod, cpuSum] of podCpu.entries()) {
const el = document.getElementById(`podcpu-${cssSafeId(pod)}`);
if (el) {
@@ -878,22 +889,15 @@
const el = document.getElementById(`podmem-${cssSafeId(pod)}`);
if (el) {
const mp = podMemPct.get(pod) || 0;
el.textContent = `${formatBytes(memSum)} (${mp.toFixed(1)}%)`;
el.textContent = `${formatBytes(memSum)} (${Number(mp).toFixed(1)}%)`;
el.classList.remove('stale');
}
}
});
containersStatsES.onerror = () => {
} catch (e) {
resetPodTotals();
};
}
function stopContainersStatsStream() {
if (!containersStatsES) return;
containersStatsES.close();
containersStatsES = null;
resetPodTotals();
} finally {
containersDashboardStatsInFlight = false;
}
}
function resetPodTotals() {