add Docker container stats endpoint + dashboard section

This commit is contained in:
2026-05-23 15:30:06 -04:00
parent 2f588e78ac
commit 5bb34e728c
2 changed files with 134 additions and 1 deletions
+63 -1
View File
@@ -179,6 +179,68 @@ async def health():
return {"status": "ok"}
@app.get("/api/docker/stats")
async def docker_stats():
"""Get Docker container stats from CT 100 via Proxmox SSH."""
import subprocess
try:
# Run docker stats on CT 100 via proxmox (use raw output to avoid Go template escaping)
result = subprocess.run(
["sshpass", "-p", _PVE_PASS, "ssh", "-o", "StrictHostKeyChecking=no",
"root@192.168.0.144", "pct", "exec", "100", "--",
"docker", "stats", "--no-stream"],
capture_output=True, text=True, timeout=15
)
if result.returncode != 0:
return {"error": result.stderr.strip(), "containers": []}
containers = []
for line in result.stdout.strip().split("\n"):
if not line.strip() or line.startswith("CONTAINER"):
continue
# Parse default docker stats output:
# CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
parts = line.split()
if len(parts) < 8:
continue
# Account for container ID being first column
cid = parts[0]
name = parts[1]
cpu = parts[2].replace("%", "")
mem_usage = parts[3] # e.g., "311.2MiB"
mem_limit = parts[5] # e.g., "2GiB" (parts[4] is "/")
mem_pct = parts[6].replace("%", "")
# NET I/O: parts[7] and parts[9] with "/" at parts[8]
# BLOCK I/O: parts[10] and parts[12] with "/" at parts[11]
# PIDS: last element (could be at different positions depending on name length)
# More reliable: find from the right
pids = parts[-1]
# Block I/O is tricky because of spaces in column names;
# use a simpler approach: rest of line after NET I/O position
# Actually, docker stats columns are fixed-width, let's just grab what we can
net_in = parts[7] if len(parts) > 7 else "0"
net_out = parts[9] if len(parts) > 9 else "0"
blk_in = parts[10] if len(parts) > 10 else "0"
blk_out = parts[12] if len(parts) > 12 else "0"
containers.append({
"name": name,
"cpu": cpu,
"mem_pct": mem_pct,
"mem_used": mem_usage,
"mem_limit": mem_limit,
"net_in": net_in,
"net_out": net_out,
"blk_in": blk_in,
"blk_out": blk_out,
"pids": pids,
})
return {"containers": containers, "host": "CT 100 (docker)", "count": len(containers)}
except Exception as e:
return {"error": str(e)[:200], "containers": []}
@app.get("/{path:path}")
async def index(path: str = ""):
"""Serve static files, fallback to index.html."""
@@ -188,7 +250,7 @@ async def index(path: str = ""):
file_path = STATIC_DIR / "index.html"
if file_path.is_file():
return HTMLResponse(file_path.read_text())
return HTMLResponse("<h1>404</h1>", 404)
return HTMLResponse("<h1>404</h1>\n", 404)
if __name__ == "__main__":