diff --git a/README.md b/README.md index 57d7936..4563430 100644 --- a/README.md +++ b/README.md @@ -152,11 +152,13 @@ as intuitive as Dockge, as capable as Portainer for Compose workflows. - The dashboard now shows, **per host** (local + each registered agent, online dot / offline notice), a **resource overview bar** (CPU cores, memory - used/total, containers, Docker version) and a **stacks-with-usage table** with - the same CPU/memory meters and inline start/stop/restart. + used/total, disk used/total, containers, Docker version) and a + **stacks-with-usage table** with CPU/memory meters and inline + start/stop/restart. - New agent endpoint `/agent/stacks/stats` (proxied at `/api/agents/{id}/stacks/stats`); `/agent/system` now also reports `cpu_cores`, - `mem_total` and `mem_used` so the remote resource bar and meters have a host + `mem_total`, `mem_used`, and `disk_total`/`disk_used` (disk of the host volume + backing the stacks dir) so the remote resource bar and meters have a host reference. ### Phase 16 — Volumes page (multi-host) diff --git a/backend/agent_app.py b/backend/agent_app.py index 849a76b..5bf39ae 100644 --- a/backend/agent_app.py +++ b/backend/agent_app.py @@ -12,6 +12,7 @@ from __future__ import annotations import logging import os +import shutil from dataclasses import asdict import tempfile @@ -61,7 +62,7 @@ def _map_docker(exc: DockerError): raise HTTPException(status_code=code, detail=exc.detail or exc.error) raise exc # falls through to the global 502 DockerError handler -AGENT_VERSION = "0.20.0" +AGENT_VERSION = "0.21.0" # --------------------------------------------------------------------------- # @@ -180,6 +181,17 @@ def _mem_info() -> tuple[int, int]: return 0, 0 +def _disk_info() -> tuple[int, int]: + """Return (total_bytes, used_bytes) for the host disk backing the stacks dir.""" + for path in (settings.STACKS_DIR, "/"): + try: + usage = shutil.disk_usage(path) + return usage.total, usage.used + except OSError: + continue + return 0, 0 + + def _system_info() -> dict: docker_version = "" host_os = "" @@ -194,6 +206,7 @@ def _system_info() -> dict: except DockerError as exc: docker_version = f"unavailable ({exc.error})" mem_total, mem_used = _mem_info() + disk_total, disk_used = _disk_info() return { "hostname": _hostname(), "docker_version": docker_version, @@ -201,6 +214,8 @@ def _system_info() -> dict: "cpu_cores": os.cpu_count() or 0, "mem_total": mem_total, "mem_used": mem_used, + "disk_total": disk_total, + "disk_used": disk_used, "containers_running": running, "containers_total": total, } diff --git a/backend/main.py b/backend/main.py index c41f611..a871f00 100644 --- a/backend/main.py +++ b/backend/main.py @@ -55,7 +55,7 @@ async def lifespan(app: FastAPI): schedule_task.cancel() -app = FastAPI(title="StackPilot", version="0.20.0", lifespan=lifespan) +app = FastAPI(title="StackPilot", version="0.21.0", lifespan=lifespan) app.add_middleware( CORSMiddleware, diff --git a/frontend/package.json b/frontend/package.json index e60d705..b129b70 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "stackpilot-frontend", "private": true, - "version": "0.20.0", + "version": "0.21.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/api/agents.ts b/frontend/src/api/agents.ts index 9ce073d..494f9e5 100644 --- a/frontend/src/api/agents.ts +++ b/frontend/src/api/agents.ts @@ -11,6 +11,8 @@ export interface AgentSystem { cpu_cores: number; mem_total: number; mem_used: number; + disk_total: number; + disk_used: number; containers_running: number; containers_total: number; } diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 5dcf30e..a96f3ea 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -11,6 +11,7 @@ import { Play, Square, RotateCw, + Server, } from "lucide-react"; import { toast } from "sonner"; import { Card, Spinner, StatusDot, Badge } from "@/components/ui"; @@ -62,6 +63,8 @@ export function Dashboard() { cpuCores={info.data?.cpu_cores ?? 0} memUsed={info.data?.ram.used ?? 0} memTotal={info.data?.ram.total ?? 0} + diskUsed={info.data?.disk.used ?? 0} + diskTotal={info.data?.disk.total ?? 0} containersRunning={info.data?.containers_running ?? 0} containersTotal={info.data?.containers_total ?? 0} dockerVersion={info.data?.docker_version ?? ""} @@ -168,6 +171,8 @@ function AgentDashboardSection({ agent, isAdmin }: { agent: Agent; isAdmin: bool cpuCores={sys.data?.cpu_cores ?? 0} memUsed={sys.data?.mem_used ?? 0} memTotal={sys.data?.mem_total ?? 0} + diskUsed={sys.data?.disk_used ?? 0} + diskTotal={sys.data?.disk_total ?? 0} containersRunning={sys.data?.containers_running ?? 0} containersTotal={sys.data?.containers_total ?? 0} dockerVersion={sys.data?.docker_version ?? ""} @@ -420,6 +425,8 @@ function ResourceBar({ cpuCores, memUsed, memTotal, + diskUsed, + diskTotal, containersRunning, containersTotal, dockerVersion, @@ -427,24 +434,31 @@ function ResourceBar({ cpuCores: number; memUsed: number; memTotal: number; + diskUsed: number; + diskTotal: number; containersRunning: number; containersTotal: number; dockerVersion: string; }) { return ( -
+
} label="CPU cores" value={cpuCores || "—"} /> } label="Memory" value={memTotal ? `${formatBytes(memUsed)} / ${formatBytes(memTotal)}` : "—"} /> + } + label="Disk" + value={diskTotal ? `${formatBytes(diskUsed)} / ${formatBytes(diskTotal)}` : "—"} + /> } label="Containers" value={`${containersRunning} / ${containersTotal}`} /> - } label="Docker" value={dockerVersion || "—"} /> + } label="Docker" value={dockerVersion || "—"} />
); }