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 @@ +

System

+
+
+
Host
+
{{ data.system.name or "—" }}
+
{{ data.system.operating_system }} · {{ data.system.kernel_version }}
+
+ +
+
Compute
+
{{ data.system.cpus or "—" }} CPUs
+
{{ data.system.mem_total_h or "—" }} RAM
+
+ +
+
Docker
+
Engine: {{ data.system.server_version or "—" }}
+
Images: {{ data.system.images or "—" }}
+
Containers: {{ data.system.containers_running or "—" }} running / {{ + data.system.containers_stopped or "—" }} stopped
+
+
+ +

Docker disk usage

+ + + + + + + + + + + + {% for typ, r in data.system.system_df.items() %} + + + + + + + + {% endfor %} + +
TypeTotalActiveSizeReclaimable
{{ typ }}{{ r.total }}{{ r.active }}{{ r.size }}{{ r.reclaimable }}
+
Generated at: {{ data.generated_at }}
{% if data.warnings %}