feat (health): voeg helper socket check toe, drie visuele states

Backend (/api/health):
- Importeer HELPER_SOCKET uit common.py
- Voeg helper-check toe: connect() op /run/podman-helper.sock, timeout=2s
- ok blijft true als alleen de helper ontbreekt (waarschuwing, geen fout)
- Nieuwe response key: "helper": {"ok": bool}

Frontend (pingApi / setApiState):
- pingApi() roept nu /api/health aan i.p.v. /pods-dashboard
- setApiState(state, msg) accepteert 'ok' / 'warn' / 'error'
- Gele dot met --warn kleur als helper.ok=false maar core OK
- refreshActive() delegeert statusupdate aan pingApi()
- Detailbericht bij fout: toont welk component (podman/systemd) faalt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 08:06:38 +01:00
parent e469508570
commit 5e7d1b887c
2 changed files with 31 additions and 10 deletions
+12
View File
@@ -1,8 +1,10 @@
import os import os
import socket
import subprocess import subprocess
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from common import ( from common import (
HELPER_SOCKET,
_helper_call, _helper_call,
_podman_get_json as _common_podman_get_json, _podman_get_json as _common_podman_get_json,
_systemctl as _common_systemctl, _systemctl as _common_systemctl,
@@ -40,11 +42,21 @@ def init_system_router(session, podman_api_base: str, workloads_dir: str) -> API
except Exception: except Exception:
systemd_reachable = False systemd_reachable = False
helper_ok = False
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.settimeout(2)
s.connect(HELPER_SOCKET)
helper_ok = True
except Exception:
helper_ok = False
ok = podman_ok and systemd_reachable ok = podman_ok and systemd_reachable
return { return {
"ok": ok, "ok": ok,
"podman": {"ok": podman_ok}, "podman": {"ok": podman_ok},
"systemd_user": {"reachable": systemd_reachable}, "systemd_user": {"reachable": systemd_reachable},
"helper": {"ok": helper_ok},
} }
@router.get("/test-hybrid") @router.get("/test-hybrid")
+19 -10
View File
@@ -624,21 +624,30 @@
// ---- Health / Ping ---- // ---- Health / Ping ----
async function pingApi() { async function pingApi() {
try { try {
// simpele ping: pods ophalen const h = await api('/health', 'GET');
await api('/pods-dashboard', 'GET'); const helperOk = h?.helper?.ok === true;
setApiState(true, 'API: OK'); if (!h?.ok) {
const detail = !h?.podman?.ok ? 'podman' : !h?.systemd_user?.reachable ? 'systemd' : 'onbekend';
setApiState('error', `API: fout (${detail})`);
} else if (!helperOk) {
setApiState('warn', 'API: OK | ⚠️ helper');
} else {
setApiState('ok', 'API: OK');
}
} catch (e) { } catch (e) {
setApiState(false, 'API: fout (' + e.message + ')'); setApiState('error', 'API: fout (' + e.message + ')');
showModal('API fout', e.stack || e.message); showModal('API fout', e.stack || e.message);
} }
} }
function setApiState(ok, msg) { function setApiState(state, msg) {
const dot = document.getElementById('apiDot'); const dot = document.getElementById('apiDot');
dot.style.background = ok ? 'var(--ok)' : 'var(--bad)'; const ok = state === 'ok';
dot.style.boxShadow = ok ? '0 0 0 6px rgba(45,212,191,.15)' : '0 0 0 6px rgba(251,113,133,.15)'; const warn = state === 'warn';
dot.style.background = ok ? 'var(--ok)' : warn ? 'var(--warn, #f59e0b)' : 'var(--bad)';
dot.style.boxShadow = ok ? '0 0 0 6px rgba(45,212,191,.15)' : warn ? '0 0 0 6px rgba(245,158,11,.15)' : '0 0 0 6px rgba(251,113,133,.15)';
document.getElementById('statusLine').textContent = msg; document.getElementById('statusLine').textContent = msg;
const apiStat = document.getElementById('dashboardApiState'); const apiStat = document.getElementById('dashboardApiState');
if (apiStat) apiStat.textContent = ok ? 'OK' : 'Fout'; if (apiStat) apiStat.textContent = ok ? 'OK' : warn ? 'Waarschuwing' : 'Fout';
} }
function currentClockText() { function currentClockText() {
@@ -672,10 +681,10 @@
const nCount = Array.isArray(networks?.networks) ? networks.networks.length : 0; const nCount = Array.isArray(networks?.networks) ? networks.networks.length : 0;
updateNavCount('countNavNetworks', nCount); updateNavCount('countNavNetworks', nCount);
} }
setApiState(true, 'API: OK');
setLastRefreshNow(); setLastRefreshNow();
pingApi();
} catch (e) { } catch (e) {
setApiState(false, 'API: fout (' + e.message + ')'); setApiState('error', 'API: fout (' + e.message + ')');
} }
} }