/** * javascript/dashboard.js * * Dashboard panel: plain-count stat tiles (Running/Stopped/Pods/Images/ * Volumes/Networks) plus a single "Resource Usage" card (CPU/Memory/Swap/ * Storage as meter rows) — kept as two visually distinct groups rather * than forcing bar-and-percentage metrics into the same tile shape as * simple counts, which is what produced the awkward spanning-tile/dead- * grid-cell layout this replaced. Both fed by ajax/system.php?action= * summary, plus the Autostart Queue table fed by action=autostart_queue. * A live scrolling event feed is deliberately out of scope here — libpod * has no "recent events for a container fleet" convenience endpoint, only * raw /events streaming, which is a separate feature (its own connection * lifecycle, not a snapshot this summary call can produce) rather than * something to fake with sample data. */ (function () { 'use strict'; const P = window.Podman; function render(summary) { if (!summary.reachable) { P.el('podman-panel-dashboard').innerHTML = '
' + 'Cannot reach the Podman API socket (' + P.escapeHtml(summary.socketPath) + '). ' + 'Is rc.podman running? Try rc.podman status from a terminal.' + '
'; return; } P.el('stat-running').textContent = summary.containers.running; P.el('stat-running').className = summary.containers.running > 0 ? 'tone-good' : ''; P.el('stat-running-total').textContent = '/ ' + summary.containers.total; P.el('stat-stopped').textContent = summary.containers.stopped; P.el('stat-stopped').classList.toggle('tone-bad', summary.containers.stopped > 0); P.el('stat-pods').textContent = summary.pods; P.el('stat-images').textContent = summary.images; P.el('stat-volumes').textContent = summary.volumes; P.el('stat-networks').textContent = summary.networks; renderResourceUsage(summary.host, summary.storage); const meta = P.el('podman-header-meta'); if (meta) { meta.innerHTML = ' podman.sock connected' + (summary.podmanVersion ? ' · v' + P.escapeHtml(summary.podmanVersion) : '') + ' · ' + summary.containers.running + ' of ' + summary.containers.total + ' containers running'; } } // Bars stay accent-colored under normal load and only shift to warn/bad // once usage is high enough to actually be worth noticing at a glance — // a bar that's always the same color regardless of value doesn't tell // you anything a number alone didn't. function barSeverityClass(percent) { if (percent === null || percent === undefined) { return ''; } if (percent >= 90) { return 'bad'; } if (percent >= 75) { return 'warn'; } return ''; } function resourceRow(label, valueText, percent) { const pct = percent === null || percent === undefined ? 0 : percent; return ( '
' + '' + P.escapeHtml(label) + '' + '
' + '
' + '' + pct + '%' + '
' + '' + P.escapeHtml(valueText) + '' + '
' ); } function renderResourceUsage(host, storage) { if (!host || !storage) { return; } let html = resourceRow('CPU (' + host.cpuCount + ')', '', host.cpuPercent) + resourceRow('Memory', P.formatBytes(host.memUsedBytes) + ' / ' + P.formatBytes(host.memTotalBytes), host.memPercent); if (host.swapTotalBytes > 0) { html += resourceRow('Swap', P.formatBytes(host.swapUsedBytes) + ' / ' + P.formatBytes(host.swapTotalBytes), Math.round(host.swapUsedBytes / host.swapTotalBytes * 100)); } html += resourceRow('Storage', storage.graphUsedFormatted + (storage.graphAllocatedBytes ? ' / ' + storage.graphAllocatedFormatted : '') + ' (' + storage.imagesSizeFormatted + ' images)', storage.graphUsedPercent); if (host.uptime) { html += '
Uptime: ' + P.escapeHtml(host.uptime) + '
'; } P.el('dashboard-resource-rows').innerHTML = html; } const STATUS_CHIP_CLASS = { started: 'podman-chip-good', stopped: 'podman-chip-neutral', failed: 'podman-chip-bad', 'safe-mode': 'podman-chip-warn', unknown: 'podman-chip-neutral', }; function renderAutostartQueue(data) { const tbody = P.el('dashboard-autostart-tbody'); if (!tbody) { return; } const entries = data.entries || []; if (!entries.length) { tbody.innerHTML = 'No containers configured for autostart.'; return; } tbody.innerHTML = entries.map(function (e) { const chipClass = STATUS_CHIP_CLASS[e.status] || 'podman-chip-neutral'; return '' + '' + e.position + '' + '' + P.escapeHtml(e.name) + '' + '' + (e.delaySeconds > 0 ? e.delaySeconds + 's' : '—') + '' + '' + P.escapeHtml(e.statusLabel) + '' + ''; }).join(''); } function load() { return P.get('system', 'summary').then(function (summary) { render(summary); if (summary.reachable) { return P.get('system', 'autostart_queue').then(renderAutostartQueue); } }).catch(function (err) { P.el('podman-panel-dashboard').innerHTML = '
' + P.escapeHtml(err.message) + '
'; }); } P.registerPanel('dashboard', { init: load, refresh: load, autoRefresh: true }); })();