/* * SysDeck - Containers Panel (v0.0.35) * Author: Jeremy Anderson (https://dcos.net) * * Podman container management panel. The Kata portion of the * former merged "Containers & VMs" module is now its own sidebar * entry — `SysDeck Kata` (plugins/sysdeck-kata/) — per the v0.0.35 * user directive: "kata containers should be called SysDeck Kata and * moved out of the tools area." * * One module, one concern: this panel manages Podman containers * only; the SysDeck Kata plugin owns Kata sandboxes & VMs. * * Bridge surface (see shared/bridge.js → bridge.containers): * list() → podman ps --format json (normalized) * inspect(id) → podman inspect * action(id, action) → podman stop|restart|rm * count() → derived from list() */ export async function mount(panel, { bridge, EventBus }) { panel.innerHTML = renderShell(); await renderPodmanTable(panel, { bridge, EventBus }); EventBus.emit('containers.loaded', {}); } // ── Shell ────────────────────────────────────────────────────────── function renderShell() { return `

Containers

Podman runtime — list, inspect, stop / restart / remove

Loading containers…

Kata Containers (hardware-virtualized OCI sandboxes) lives in its own sidebar entry — SysDeck Kata — real sandbox and VM state from the host, not a bundle mock.

`; } async function renderPodmanTable(panel, { bridge, EventBus }) { const host = panel.querySelector('#containers-table-host'); const summary = panel.querySelector('#containers-summary'); if (!host || !summary) return; host.innerHTML = `
`; let containers = []; try { containers = await bridge.containers.list(); } catch (err) { summary.textContent = 'Podman unavailable'; host.innerHTML = renderPodmanError(err); return; } summary.textContent = `Podman runtime — ${containers.length} container${containers.length === 1 ? '' : 's'}`; host.innerHTML = ` ${containers.map(renderPodmanRow).join('') || ''}
IDNameImageStatusPortsActions
No containers. Run `podman run -d --name hello alpine sleep 9999` to create one.
`; host.querySelectorAll('[data-action]').forEach((btn) => { btn.addEventListener('click', async () => { const id = btn.dataset.id; const action = btn.dataset.action; btn.disabled = true; try { await bridge.containers.action(id, action); EventBus.emit('container.action', { id, action }); } catch (err) { EventBus.emit('container.error', { id, error: err.message }); // the operator sees the failure where they clicked it — // the table re-render alone would silently swallow it const flash = panel.querySelector('#containers-flash'); if (flash) { flash.textContent = `action failed: ${err.message || err}`; flash.style.display = 'inline-block'; clearTimeout(panel._containersFlashTimer); panel._containersFlashTimer = setTimeout(() => { flash.style.display = 'none'; }, 4000); } } renderPodmanTable(panel, { bridge, EventBus }); }); }); } function renderPodmanRow(c) { const statusClass = (c.status || '').startsWith('Up') ? 'success' : 'warn'; const id = (c.id || '').substring(0, 12); const name = c.name || '—'; const image = c.image || '—'; const status = c.status || '—'; const ports = c.ports || '—'; return ` ${escapeHtml(id)} ${escapeHtml(name)} ${escapeHtml(image)} ${escapeHtml(status)} ${escapeHtml(ports)} `; } function renderPodmanError(err) { return `

Podman unavailable

${escapeHtml(err.message || String(err))}. Install podman to manage containers from this panel.

Arch: pacman -S podman · Debian: apt install podman · Fedora: dnf install podman

`; } // ── Utilities ─────────────────────────────────────────────────────── function escapeHtml(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function renderSkeleton() { return `
`; }