Initial commit
This commit is contained in:
115
app/public/js/status.js
Executable file
115
app/public/js/status.js
Executable file
@@ -0,0 +1,115 @@
|
||||
|
||||
// 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 `<span class="port">${text || '—'}</span>`;
|
||||
}
|
||||
|
||||
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('') : '<span class="muted">No published ports</span>';
|
||||
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 = `
|
||||
<div class="name">${c.name}<div class="muted">${c.image}</div></div>
|
||||
<div><span class="${statusClass}">${c.status}</span></div>
|
||||
<div class="ports">${ports}</div>
|
||||
<div>
|
||||
<span class="badge">CPU: ${formatPercent(cpu)}</span>
|
||||
<span class="badge">Mem: ${formatMB(memMb)} (${formatPercent(memPct)})</span>
|
||||
</div>
|
||||
<div class="muted small">${c.id.slice(0,12)}</div>
|
||||
`;
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user