// Simple status dashboard const DATA_URL = 'data/status.json'; let autoRefresh = true; let refreshTimer = null; function formatMB(mb) { return `${mb.toFixed(2)} MB`; } function formatPercent(p) { return `${p.toFixed(2)}%`; } function setRing(el, percent) { const deg = Math.min(100, Math.max(0, percent)) * 3.6; // 100% -> 360deg el.style.setProperty('--deg', `${deg}deg`); } function renderSystem(system) { const cpuPercent = system?.cpu?.percent ?? 0; const ring = document.getElementById('cpu-ring'); setRing(ring, cpuPercent); document.getElementById('cpu-percent').textContent = formatPercent(cpuPercent); const la = system?.cpu?.load_avg ?? {}; document.getElementById('load-1m').textContent = (la['1m'] ?? '–'); document.getElementById('load-5m').textContent = (la['5m'] ?? '–'); document.getElementById('load-15m').textContent = (la['15m'] ?? '–'); const mem = system?.memory ?? {}; const pct = mem.percent ?? 0; document.getElementById('mem-bar').style.width = `${pct}%`; document.getElementById('mem-percent').textContent = formatPercent(pct); document.getElementById('mem-used').textContent = formatMB(mem.used_mb ?? 0); document.getElementById('mem-total').textContent = formatMB(mem.total_mb ?? 0); } function portBadge(p) { const host = p.host_ip ? `${p.host_ip}` : ''; const hostPort = p.host_port ? `:${p.host_port}` : ''; const container = p.container_port || ''; const text = `${host}${hostPort} → ${container}`.trim(); return `${text || '—'}`; } function renderContainers(list) { const q = document.getElementById('search').value.toLowerCase(); const root = document.getElementById('container-list'); root.innerHTML = ''; const filtered = list.filter(c => { const s = `${c.name} ${c.image}`.toLowerCase(); return s.includes(q); }); document.getElementById('container-count').textContent = `${filtered.length} / ${list.length} shown`; filtered.forEach(c => { const statusClass = (c.status === 'running') ? 'status' : 'status stopped'; const ports = (c.openPorts && c.openPorts.length) ? c.openPorts.map(portBadge).join('') : 'No published ports'; const cpu = c.resources?.cpu_percent ?? 0; const memMb = c.resources?.memory_mb ?? 0; const memPct = c.resources?.memory_percent ?? 0; const row = document.createElement('div'); row.className = 'row'; row.innerHTML = `
${c.name}
${c.image}
${c.status}
${ports}
CPU: ${formatPercent(cpu)} Mem: ${formatMB(memMb)} (${formatPercent(memPct)})
${c.id.slice(0,12)}
`; root.appendChild(row); }); } async function loadStatus() { try { const res = await fetch(`${DATA_URL}?t=${Date.now()}`, { cache: 'no-store' }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); document.getElementById('last-update').textContent = new Date(data.timestamp).toLocaleString(); renderSystem(data.system); renderContainers(data.containers || []); } catch (err) { console.error('Failed to load status:', err); document.getElementById('last-update').textContent = 'failed'; } } function startAutoRefresh() { if (refreshTimer) clearInterval(refreshTimer); refreshTimer = setInterval(() => { if (autoRefresh) loadStatus(); }, 5000); } // Events window.addEventListener('DOMContentLoaded', () => { document.getElementById('toggle-refresh').addEventListener('click', () => { autoRefresh = !autoRefresh; document.getElementById('toggle-refresh').textContent = autoRefresh ? 'Pause' : 'Resume'; document.getElementById('refresh-state').textContent = autoRefresh ? 'on' : 'paused'; if (autoRefresh) loadStatus(); }); document.getElementById('search').addEventListener('input', () => { // Re-render list using current data in memory by fetching again (cheap) loadStatus(); }); loadStatus(); startAutoRefresh(); });