diff --git a/app.py b/app.py index 33e3351..d1c11f5 100644 --- a/app.py +++ b/app.py @@ -48,6 +48,68 @@ def docker_stats() -> dict: } return stats +import re + +def docker_info() -> dict: + # docker info --format "{{json .}}" gives us structured host-level info + out = sh([DOCKER, "info", "--format", "{{json .}}"]) + return json.loads(out) + +def docker_system_df() -> dict: + # Parse `docker system df` (text). It's stable enough for a dashboard. + out = sh([DOCKER, "system", "df"]) + # Example lines: + # Images 175 18 15.15GB 13.93GB (91%) + # Containers 27 26 145.1MB 16.57kB (0%) + # Local Volumes 47 1 817.7MB 817.7MB (100%) + # Build Cache 889 0 423B 423B + rows = {} + for line in out.splitlines(): + line = line.strip() + if not line or line.startswith("TYPE"): + continue + parts = re.split(r"\s{2,}", line) + if len(parts) >= 5: + typ, total, active, size, reclaimable = parts[:5] + rows[typ] = { + "total": total, + "active": active, + "size": size, + "reclaimable": reclaimable, + } + return rows + +def system_summary() -> dict: + info = docker_info() + df = docker_system_df() + + return { + "name": info.get("Name", ""), + "server_version": info.get("ServerVersion", ""), + "operating_system": info.get("OperatingSystem", ""), + "os_type": info.get("OSType", ""), + "architecture": info.get("Architecture", ""), + "kernel_version": info.get("KernelVersion", ""), + "cpus": info.get("NCPU", ""), + "mem_total": info.get("MemTotal", ""), # bytes + "containers": info.get("Containers", ""), + "containers_running": info.get("ContainersRunning", ""), + "containers_stopped": info.get("ContainersStopped", ""), + "images": info.get("Images", ""), + "docker_root_dir": info.get("DockerRootDir", ""), + "system_df": df, + } + +def format_bytes(n: int) -> str: + # for mem_total + units = ["B", "KB", "MB", "GB", "TB"] + f = float(n) + for u in units: + if f < 1024 or u == units[-1]: + return f"{f:.1f}{u}" + f /= 1024 + return f"{n}B" + def docker_inspect_restart_count(container_name: str) -> int: # RestartCount is useful when stuff is flapping / OOMing try: @@ -126,11 +188,22 @@ def collect(): warnings.append(f"{a['app']} RAM high ({a['mem_pct']})") if a["restarts"] >= 3: warnings.append(f"{a['app']} restarting (restarts={a['restarts']})") + + sysinfo = system_summary() + + # format mem bytes nicely + try: + mem_total_h = format_bytes(int(sysinfo["mem_total"])) + except Exception: + mem_total_h = "" + + sysinfo["mem_total_h"] = mem_total_h return { "generated_at": datetime.utcnow().isoformat() + "Z", "poll_seconds": POLL_SECONDS, "domain": APP_DOMAIN, + "system": sysinfo, "apps": apps, "infra": infra, "warnings": warnings, diff --git a/templates/apps_table.html b/templates/apps_table.html index 4fb6ddd..2d5a4b6 100644 --- a/templates/apps_table.html +++ b/templates/apps_table.html @@ -1,3 +1,50 @@ +
| Type | +Total | +Active | +Size | +Reclaimable | +
|---|---|---|---|---|
| {{ typ }} | +{{ r.total }} | +{{ r.active }} | +{{ r.size }} | +{{ r.reclaimable }} | +