// ---- Pods ---- async function podAction(action, name) { try { const res = await api(`/pods/actions/${encodeURIComponent(action)}/${encodeURIComponent(name)}`, 'POST'); showModal(`Pod ${action}: ${name}`, JSON.stringify(res, null, 2)); await fetchContainers(); // refresh pods + containers view in één keer } catch (e) { showModal(`Pod ${action} fout`, e.stack || e.message); } } // ---- Containers ---- function _podKey(c) { return (c.PodName && String(c.PodName).trim()) ? String(c.PodName).trim() : '(geen pod)'; } function _cmpStr(a, b) { return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' }); } function _isCollapsed(pod) { return localStorage.getItem('pod_group_collapsed:' + pod) === '1'; } function _setCollapsed(pod, v) { localStorage.setItem('pod_group_collapsed:' + pod, v ? '1' : '0'); } function renderActionsDropdown(menuId, actionFn, targetEsc) { // actionFn is string: "containerAction" of "podAction" // targetEsc is al esc(...) dus veilig in onclick return ` `; } function renderContainerRow(c) { const name = (c.Names && c.Names[0]) ? c.Names[0] : (c.Names || c.Name || c.name || ''); const status = c.Status || c.State || c.state || ''; const podName = c.PodName || '-'; const image = c.Image || c.image || ''; const managed = c._dashboard_source || 'podman'; const menuId = `menu-${cssSafeId('c:' + normalizeContainerName(name))}`; const inPod = !!(c.PodName && String(c.PodName).trim()); const ports = inPod ? '' // verberg bij pod-containers : ((c._dashboard_published_ports || []).join(", ") || (c.Ports || []).map(p => `${p.host_port}:${p.container_port}`).join(", ")); return ` ${esc(name)} ${badgeFromStatus(status)} ${podName} ${esc(image)} ${badgeFromStatus(managed)} - - ${ports || '-'}
${renderActionsDropdown(menuId, 'containerAction', esc(name))}
`; } let containersC2P = new Map(); function renderContainersGrouped(list, tbody, podStatus) { const groups = new Map(); for (const c of (list || [])) { const k = _podKey(c); if (!groups.has(k)) groups.set(k, []); groups.get(k).push(c); } for (const podName of Object.keys(podStatus || {})) { if (!groups.has(podName)) groups.set(podName, []); } const keys = Array.from(groups.keys()).sort((a, b) => { if (a === '(geen pod)' && b !== '(geen pod)') return 1; if (b === '(geen pod)' && a !== '(geen pod)') return -1; return _cmpStr(a, b); }); containersC2P = new Map(); let html = ''; for (const pod of keys) { const items = groups.get(pod) || []; items.sort((a, b) => _cmpStr( (a.Names && a.Names[0]) ? a.Names[0] : (a.Name || ''), (b.Names && b.Names[0]) ? b.Names[0] : (b.Name || '') ) ); const collapsed = _isCollapsed(pod); const isRealPod = (pod !== '(geen pod)'); const total = items.length; const podMenuId = `menu-${cssSafeId('p:' + pod)}`; let podPortsText = "-"; if (isRealPod) { const podPorts = Array.from(new Set( items.flatMap(c => (c._dashboard_published_ports || [])) )).sort((a, b) => _cmpStr(a, b)); podPortsText = podPorts.length ? podPorts.join(", ") : "-"; } let cls = 'muted'; let label = '-'; if (total > 0) { const running = items.filter(x => { const s = (x.Status || x.State || '').toLowerCase(); return s === 'running'; }).length; label = `${running}/${total}`; if (running === total) cls = 'ok'; else if (running === 0) cls = 'bad'; else cls = 'warn'; } else { const ps = String((podStatus && podStatus[pod]) || '').toLowerCase(); if (ps === 'active') { cls = 'ok'; label = 'active'; } else if (ps === 'inactive') { cls = 'bad'; label = 'inactive'; } else { cls = 'muted'; label = ps || 'unknown'; } } html += ` ${collapsed ? '▶' : '▼'} ${esc(pod)} ${label} - - ${isRealPod ? 'pod' : '-'} - - ${esc(podPortsText)} ${isRealPod ? `
${renderActionsDropdown(podMenuId, 'podAction', esc(pod))}
` : '-'} `; for (const c of items) { const cname = normalizeContainerName((c.Names && c.Names[0]) ? c.Names[0] : (c.Names || c.Name || c.name || '')); if (cname) containersC2P.set(cname, pod); const row = renderContainerRow(c).replace( '