Phase 17: multi-host dashboard (0.19.0)

The dashboard now renders a stacks-with-usage table per host: the local host
plus a section for each registered agent (online dot + offline notice), reusing
the same CPU/memory meters and inline start/stop/restart actions.

- agent_app.py: GET /agent/stacks/stats (reuses stats_service); /agent/system
  now also returns cpu_cores + mem_total for remote meter references.
- routers/agents.py: proxy GET /api/agents/{id}/stacks/stats (declared before
  /{agent_id}/stacks/{stack_id}).
- Frontend: agentsApi.system + stackStats; Dashboard refactored into a shared
  StacksTable used by the local section and a per-agent AgentDashboardSection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
menzelj
2026-06-08 13:15:33 +00:00
co-authored by Claude Opus 4.8
parent 56450efd82
commit 8fbacd200a
7 changed files with 234 additions and 67 deletions
+15 -1
View File
@@ -1,9 +1,19 @@
import api from "./client";
import type { Agent, StackDetail, StackSummary } from "@/types";
import type { Agent, StackDetail, StackStats, StackSummary } from "@/types";
export type RemoteStackSummary = StackSummary & { agent_id: number; agent_name: string };
export type RemoteStackDetail = StackDetail & { agent_id: number; agent_name: string };
export interface AgentSystem {
hostname: string;
docker_version: string;
host_os: string;
cpu_cores: number;
mem_total: number;
containers_running: number;
containers_total: number;
}
export const agentsApi = {
list: (refresh = true) =>
api.get<Agent[]>(`/api/agents?refresh=${refresh}`).then((r) => r.data),
@@ -15,8 +25,12 @@ export const agentsApi = {
ping: (id: number) =>
api.post<{ status: string; hostname?: string }>(`/api/agents/${id}/ping`).then((r) => r.data),
system: (id: number) =>
api.get<AgentSystem>(`/api/agents/${id}/system`).then((r) => r.data),
stacks: (id: number) =>
api.get<RemoteStackSummary[]>(`/api/agents/${id}/stacks`).then((r) => r.data),
stackStats: (id: number) =>
api.get<Record<string, StackStats>>(`/api/agents/${id}/stacks/stats`).then((r) => r.data),
createStack: (id: number, body: { name: string; yaml: string; env?: string }) =>
api
.post<{ id: string; name: string }>(`/api/agents/${id}/stacks`, body)