From 8e4e0067ffc70eb092747419a0823d171bd345f5 Mon Sep 17 00:00:00 2001 From: kodi Date: Wed, 25 Feb 2026 14:42:03 +0100 Subject: [PATCH] feat(api): add cached container cpu/mem fields on containers-dashboard --- control/app.py | 1 + webui/html/index.html | 96 ++++++++++++++++++++++--------------------- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/control/app.py b/control/app.py index 8a86a7c..e480990 100644 --- a/control/app.py +++ b/control/app.py @@ -70,6 +70,7 @@ async def _stats_poller_loop(): key = _norm_container_name(st.get("Name")) if not key: continue + # CPUPerc returned by Podman is already percentage (0.10 == 0.10%) cpu_val = st.get("CPUPerc") if cpu_val is None: cpu_val = st.get("CPU") diff --git a/webui/html/index.html b/webui/html/index.html index ea2c43a..9b91990 100644 --- a/webui/html/index.html +++ b/webui/html/index.html @@ -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() {