diff --git a/webui/html/index.html b/webui/html/index.html
index ab2efb5..36708d8 100644
--- a/webui/html/index.html
+++ b/webui/html/index.html
@@ -220,6 +220,14 @@
overflow:auto;
}
.hint{font-size:12px;color:var(--muted);margin-top:8px;line-height:1.35}
+ .pod-group-row td {
+ background: rgba(255,255,255,0.04);
+ border-top: 1px solid rgba(255,255,255,0.08);
+ font-weight: 600;
+ }
+ .pod-group-row:hover td {
+ background: rgba(255,255,255,0.07);
+ }
@@ -623,42 +631,131 @@
}
// ---- 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 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 ports = (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 || '-'} |
+
+
+
+
+
+
+
+
+ |
+
+ `;
+ }
+
+ function renderContainersGrouped(list, tbody) {
+ 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);
+ }
+
+ 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);
+ });
+
+ const table = tbody.closest('table');
+ const colCount = table ? table.querySelectorAll('thead th').length : 9;
+
+ 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);
+
+ html += `
+
+ |
+
+ ${collapsed ? '▶' : '▼'} ${esc(pod)} (${items.length})
+
+ |
+
+ `;
+
+ for (const c of items) {
+ const row = renderContainerRow(c).replace(
+ ' {
+ const t = ev.target.closest('.pod-toggle');
+ if (!t) return;
+
+ const pod = t.getAttribute('data-pod');
+ const isNowCollapsed = !_isCollapsed(pod);
+ _setCollapsed(pod, isNowCollapsed);
+
+ tbody.querySelectorAll(`.pod-item-row[data-pod="${CSS.escape(pod)}"]`).forEach(r => {
+ r.style.display = isNowCollapsed ? 'none' : '';
+ });
+
+ t.innerHTML =
+ `${isNowCollapsed ? '▶' : '▼'} ${pod} ` +
+ `(${tbody.querySelectorAll(`.pod-item-row[data-pod="${CSS.escape(pod)}"]`).length})`;
+ };
+ }
+
async function fetchContainers() {
const containers = await api('/containers-dashboard', 'GET');
const list = Array.isArray(containers) ? containers : (containers?.containers || []);
document.getElementById('countContainers').textContent = list.length;
const tbody = document.getElementById('containersTbody');
- tbody.innerHTML = list.map(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 ports = (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 || '-'} |
-
-
-
-
-
-
-
-
- |
-
- `;
- }).join('');
+ renderContainersGrouped(list, tbody);
}
let containersStatsES = null;