home: add Containers + Cluster cards, fix Editor link, extend loadStats

This commit is contained in:
Zac Gaetano 2026-05-20 00:02:48 -04:00
parent 0c761d553c
commit 879c547e08

View file

@ -234,6 +234,14 @@
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="6" cy="10" r="3.5"/><path d="M8.7 7.3L13 3M11.5 3.5l1.5 1.5M13.5 2.5l1 1"/></svg>
Tokens
</a>
<a href="containers.html" class="nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="5" width="14" height="4" rx="1"/><rect x="1" y="10" width="14" height="4" rx="1"/><path d="M4 7h1M4 12h1"/></svg>
Containers
</a>
<a href="cluster.html" class="nav-item">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="2"/><circle cx="2" cy="3" r="1.5"/><circle cx="14" cy="3" r="1.5"/><circle cx="2" cy="13" r="1.5"/><circle cx="14" cy="13" r="1.5"/><path d="M3.1 4.1L6.5 6.5M12.9 4.1L9.5 6.5M3.1 11.9L6.5 9.5M12.9 11.9L9.5 9.5"/></svg>
Cluster
</a>
</nav>
<div class="sidebar-footer">
<div class="sidebar-user">
@ -366,7 +374,7 @@
<div class="home-card-desc">DeckLink SDI capture with manual scene control and per-device routing.</div>
</a>
<a href="edit.html" class="home-card">
<a href="editor.html" class="home-card">
<div class="home-card-title">Editor</div>
<div class="home-card-preview">
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
@ -410,6 +418,66 @@
<div class="home-card-desc">Track proxy generation, thumbnails, and AMPP folder sync as they run.</div>
</a>
<a href="containers.html" class="home-card">
<div class="home-card-title">Containers</div>
<div class="home-card-preview">
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
<!-- stacked container boxes -->
<g fill="oklch(20% 0.04 260)" stroke="oklch(45% 0.10 266 / 0.45)" stroke-width="0.6">
<rect x="20" y="18" width="160" height="22" rx="3"/>
<rect x="20" y="46" width="160" height="22" rx="3"/>
<rect x="20" y="74" width="160" height="22" rx="3"/>
<rect x="20" y="102" width="160" height="16" rx="3"/>
</g>
<!-- status dots -->
<circle cx="34" cy="29" r="3.5" fill="oklch(62% 0.22 145)"/>
<circle cx="34" cy="57" r="3.5" fill="oklch(62% 0.22 145)"/>
<circle cx="34" cy="85" r="3.5" fill="oklch(62% 0.22 145)"/>
<circle cx="34" cy="110" r="3.5" fill="oklch(62% 0.22 25)"/>
<!-- label bars -->
<rect x="46" y="25" width="60" height="5" rx="2" fill="oklch(50% 0.12 266 / 0.7)"/>
<rect x="46" y="53" width="80" height="5" rx="2" fill="oklch(50% 0.12 266 / 0.7)"/>
<rect x="46" y="81" width="50" height="5" rx="2" fill="oklch(50% 0.12 266 / 0.7)"/>
<rect x="46" y="107" width="70" height="4" rx="2" fill="oklch(40% 0.10 266 / 0.5)"/>
</svg>
<span class="home-card-stats"><span class="home-card-stats-dot"></span><b id="containerCount">--</b>&nbsp;running</span>
</div>
<div class="home-card-desc">Manage Docker Compose services — start, stop, and restart containers.</div>
</a>
<a href="cluster.html" class="home-card">
<div class="home-card-title">Cluster</div>
<div class="home-card-preview">
<svg class="preview-art" viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="200" height="125" fill="oklch(13% 0.02 250)"/>
<!-- hub -->
<circle cx="100" cy="62" r="10" fill="oklch(20% 0.04 260)" stroke="oklch(55% 0.20 266 / 0.7)" stroke-width="1"/>
<circle cx="100" cy="62" r="4" fill="oklch(70% 0.18 266)"/>
<!-- spokes -->
<line x1="100" y1="62" x2="36" y2="22" stroke="oklch(45% 0.12 266 / 0.4)" stroke-width="1"/>
<line x1="100" y1="62" x2="164" y2="22" stroke="oklch(45% 0.12 266 / 0.4)" stroke-width="1"/>
<line x1="100" y1="62" x2="24" y2="80" stroke="oklch(45% 0.12 266 / 0.4)" stroke-width="1"/>
<line x1="100" y1="62" x2="176" y2="80" stroke="oklch(45% 0.12 266 / 0.4)" stroke-width="1"/>
<line x1="100" y1="62" x2="100" y2="108" stroke="oklch(45% 0.12 266 / 0.4)" stroke-width="1"/>
<!-- satellite nodes -->
<circle cx="36" cy="22" r="7" fill="oklch(18% 0.03 260)" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.6"/>
<circle cx="164" cy="22" r="7" fill="oklch(18% 0.03 260)" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.6"/>
<circle cx="24" cy="80" r="7" fill="oklch(18% 0.03 260)" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.6"/>
<circle cx="176" cy="80" r="7" fill="oklch(18% 0.03 260)" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.6"/>
<circle cx="100" cy="108" r="7" fill="oklch(18% 0.03 260)" stroke="oklch(45% 0.10 266 / 0.5)" stroke-width="0.6"/>
<!-- online dots -->
<circle cx="36" cy="22" r="2.5" fill="oklch(62% 0.22 145)"/>
<circle cx="164" cy="22" r="2.5" fill="oklch(62% 0.22 145)"/>
<circle cx="24" cy="80" r="2.5" fill="oklch(62% 0.22 145)"/>
<circle cx="176" cy="80" r="2.5" fill="oklch(55% 0.18 80)"/>
<circle cx="100" cy="108" r="2.5" fill="oklch(45% 0.10 266 / 0.4)"/>
</svg>
<span class="home-card-stats"><span class="home-card-stats-dot"></span><b id="nodeCount">--</b>&nbsp;nodes</span>
</div>
<div class="home-card-desc">Multi-server cluster registry. Monitor, federate, and scale Z-AMPP nodes.</div>
</a>
<a href="tokens.html" class="home-card" title="Just kidding">
<div class="home-card-title">Tokens</div>
<div class="home-card-preview">
@ -441,11 +509,13 @@
async function loadStats() {
const setText = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
try {
const [aRes, pRes, rRes, jRes] = await Promise.allSettled([
const [aRes, pRes, rRes, jRes, cRes, nRes] = await Promise.allSettled([
fetch('/api/v1/assets?limit=1', { credentials: 'include' }),
fetch('/api/v1/projects', { credentials: 'include' }),
fetch('/api/v1/recorders', { credentials: 'include' }),
fetch('/api/v1/jobs?status=active', { credentials: 'include' }),
fetch('/api/v1/system/containers', { credentials: 'include' }),
fetch('/api/v1/cluster', { credentials: 'include' }),
]);
if (aRes.status === 'fulfilled' && aRes.value.ok) {
@ -469,7 +539,19 @@
setText('jobCount', arr.length);
setText('ingestCount', arr.filter(x => (x.type || '').toLowerCase().includes('proxy')).length || arr.length);
}
const tb = document.getElementById('tokenBurn'); if (tb) { tb.textContent = (14000 + Math.round(Math.random() * 8000)).toLocaleString(); }
if (cRes.status === 'fulfilled' && cRes.value.ok) {
const j = await cRes.value.json();
const arr = Array.isArray(j) ? j : (j.containers ?? []);
const running = arr.filter(c => c.state === 'running').length;
setText('containerCount', running);
}
if (nRes.status === 'fulfilled' && nRes.value.ok) {
const j = await nRes.value.json();
const arr = Array.isArray(j) ? j : (j.nodes ?? []);
const online = arr.filter(n => n.online).length;
setText('nodeCount', arr.length > 0 ? online + '/' + arr.length : '--');
}
const tb = document.getElementById('tokenBurn'); if (tb) { tb.textContent = (14000 + Math.round(Math.random() * 8000)).toLocaleString(); }
} catch (_) { /* leave dashes */ }
}
loadStats();