Dashboard: per-host resource overview bar (0.20.0)

The dashboard resource bar (CPU cores, memory used/total, containers, Docker
version) is now rendered per host instead of once for the local host — each
host section (local + each agent) shows its own bar above its stacks table.

- agent_app.py: _system_info() now also returns mem_used (from meminfo
  available), alongside the cpu_cores/mem_total added in 0.19.0.
- Frontend: extracted a ResourceBar component used by the local section and each
  AgentDashboardSection; AgentSystem type gained mem_used.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
menzelj
2026-06-08 13:22:27 +00:00
co-authored by Claude Opus 4.8
parent 8fbacd200a
commit eb9ffd0b0d
6 changed files with 80 additions and 35 deletions
+1
View File
@@ -10,6 +10,7 @@ export interface AgentSystem {
host_os: string;
cpu_cores: number;
mem_total: number;
mem_used: number;
containers_running: number;
containers_total: number;
}
+53 -22
View File
@@ -51,33 +51,21 @@ export function Dashboard() {
</Link>
)}
{/* Local host resource bar */}
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
<Stat icon={<Cpu className="h-5 w-5" />} label="CPU cores" value={info.data?.cpu_cores ?? "—"} />
<Stat
icon={<MemoryStick className="h-5 w-5" />}
label="Memory"
value={
info.data
? `${formatBytes(info.data.ram.used)} / ${formatBytes(info.data.ram.total)}`
: "—"
}
/>
<Stat
icon={<Container className="h-5 w-5" />}
label="Containers"
value={info.data ? `${info.data.containers_running} / ${info.data.containers_total}` : "—"}
/>
<Stat icon={<HardDrive className="h-5 w-5" />} label="Docker" value={info.data?.docker_version ?? "—"} />
</div>
{/* Local stacks */}
{/* Local host */}
<section>
{hasAgents ? (
<HostHeader />
) : (
<h2 className="mb-3 text-sm font-semibold uppercase tracking-wide text-slate-500">Stacks</h2>
<h2 className="mb-3 text-sm font-semibold uppercase tracking-wide text-slate-500">This host</h2>
)}
<ResourceBar
cpuCores={info.data?.cpu_cores ?? 0}
memUsed={info.data?.ram.used ?? 0}
memTotal={info.data?.ram.total ?? 0}
containersRunning={info.data?.containers_running ?? 0}
containersTotal={info.data?.containers_total ?? 0}
dockerVersion={info.data?.docker_version ?? ""}
/>
<StacksTable
stacks={stacks.data}
stats={stats.data}
@@ -175,6 +163,15 @@ function AgentDashboardSection({ agent, isAdmin }: { agent: Agent; isAdmin: bool
</p>
</Card>
) : (
<>
<ResourceBar
cpuCores={sys.data?.cpu_cores ?? 0}
memUsed={sys.data?.mem_used ?? 0}
memTotal={sys.data?.mem_total ?? 0}
containersRunning={sys.data?.containers_running ?? 0}
containersTotal={sys.data?.containers_total ?? 0}
dockerVersion={sys.data?.docker_version ?? ""}
/>
<StacksTable
stacks={stacks.data}
stats={stats.data}
@@ -189,6 +186,7 @@ function AgentDashboardSection({ agent, isAdmin }: { agent: Agent; isAdmin: bool
onRestart={(id) => run("restart", "Restarting", id)}
emptyText="No stacks on this host."
/>
</>
)}
</section>
);
@@ -418,6 +416,39 @@ function Meter({
);
}
function ResourceBar({
cpuCores,
memUsed,
memTotal,
containersRunning,
containersTotal,
dockerVersion,
}: {
cpuCores: number;
memUsed: number;
memTotal: number;
containersRunning: number;
containersTotal: number;
dockerVersion: string;
}) {
return (
<div className="mb-4 grid grid-cols-2 gap-4 md:grid-cols-4">
<Stat icon={<Cpu className="h-5 w-5" />} label="CPU cores" value={cpuCores || "—"} />
<Stat
icon={<MemoryStick className="h-5 w-5" />}
label="Memory"
value={memTotal ? `${formatBytes(memUsed)} / ${formatBytes(memTotal)}` : "—"}
/>
<Stat
icon={<Container className="h-5 w-5" />}
label="Containers"
value={`${containersRunning} / ${containersTotal}`}
/>
<Stat icon={<HardDrive className="h-5 w-5" />} label="Docker" value={dockerVersion || "—"} />
</div>
);
}
function Stat({
icon,
label,