From 78e71b38955ea0fc326b5c63fca9063278ef9608 Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Tue, 23 Dec 2025 23:28:54 +1100 Subject: [PATCH] Add container info as well --- app.py | 80 ++++++- templates/admin.html | 394 ++++++++++++++++++++++++++++++++ templates/container_detail.html | 319 ++++++++++++++++++++++++++ templates/index.html | 4 +- templates/logs.html | 6 +- 5 files changed, 795 insertions(+), 8 deletions(-) create mode 100644 templates/admin.html create mode 100644 templates/container_detail.html diff --git a/app.py b/app.py index 6ddf714..64512f3 100644 --- a/app.py +++ b/app.py @@ -159,6 +159,62 @@ def get_container_logs(container_name: str, lines: int = 50) -> list[dict]: except Exception: return [] +def get_container_detail(container_name: str) -> dict: + """ + Get detailed container information using docker inspect. + Returns parsed container metadata. + """ + try: + out = sh([DOCKER, "inspect", container_name]) + inspect_data = json.loads(out) + + if not inspect_data: + return {} + + container = inspect_data[0] + + # Extract useful information + config = container.get("Config", {}) + state = container.get("State", {}) + network_settings = container.get("NetworkSettings", {}) + mounts = container.get("Mounts", []) + + return { + "name": container.get("Name", "").lstrip("/"), + "id": container.get("Id", "")[:12], + "image": config.get("Image", ""), + "created": container.get("Created", ""), + "state": { + "status": state.get("Status", ""), + "running": state.get("Running", False), + "paused": state.get("Paused", False), + "restarting": state.get("Restarting", False), + "started_at": state.get("StartedAt", ""), + "finished_at": state.get("FinishedAt", ""), + }, + "env": config.get("Env", []), + "cmd": config.get("Cmd", []), + "entrypoint": config.get("Entrypoint", []), + "working_dir": config.get("WorkingDir", ""), + "exposed_ports": list(config.get("ExposedPorts", {}).keys()), + "ports": network_settings.get("Ports", {}), + "networks": list(network_settings.get("Networks", {}).keys()), + "ip_address": network_settings.get("IPAddress", ""), + "mounts": [ + { + "type": m.get("Type", ""), + "source": m.get("Source", ""), + "destination": m.get("Destination", ""), + "mode": m.get("Mode", ""), + "rw": m.get("RW", False), + } + for m in mounts + ], + "restart_policy": container.get("HostConfig", {}).get("RestartPolicy", {}), + } + except Exception as e: + return {"error": str(e)} + def is_app_web_container(name: str) -> bool: # Dokku apps typically have containers like ".web.1" return name.endswith(".web.1") and not name.startswith("dokku.") @@ -362,7 +418,7 @@ def login(): password = request.form.get("password", "") if password == LOGS_PASSWORD: session['logged_in'] = True - return redirect(url_for('logs')) + return redirect(url_for('admin')) else: return render_template("login.html", error="Invalid password") return render_template("login.html", error=None) @@ -372,9 +428,23 @@ def logout(): session.pop('logged_in', None) return redirect(url_for('index')) -# Protected logs page -@app.get("/logs") +# Protected admin page (logs + container details) +@app.get("/admin") @login_required -def logs(): +def admin(): data = collect_logs_only() - return render_template("logs.html", data=data, poll_seconds=POLL_SECONDS) + return render_template("admin.html", data=data, poll_seconds=POLL_SECONDS) + +# Protected container detail page +@app.get("/admin/container/") +@login_required +def container_detail(container_name): + detail = get_container_detail(container_name) + return render_template("container_detail.html", container=detail, container_name=container_name) + +# API endpoint for container details (used by admin panel) +@app.get("/api/container/") +@login_required +def api_container_detail(container_name): + detail = get_container_detail(container_name) + return jsonify(detail) diff --git a/templates/admin.html b/templates/admin.html new file mode 100644 index 0000000..b58cd74 --- /dev/null +++ b/templates/admin.html @@ -0,0 +1,394 @@ + + + + + + + Admin - DokkuStatus + + + + + + + + +
+
+

[ ADMIN PANEL ]

+
+ + [LOGOUT] +
+
+ + {% for r in data.apps %} +
+
+
[{{ r.app }}]
+
+ +
+
+ +
+ +

Container Logs

+
+
[LAST 50 LINES]
+ +
+
+ {% if r.logs %} + {% for log in r.logs %} +
{{ log.text }}
+ {% endfor %} + {% else %} +
[no logs available]
+ {% endif %} +
+ + +

Container Details

+
+
+ +
+
+
+
+ {% endfor %} +
+ + + + + \ No newline at end of file diff --git a/templates/container_detail.html b/templates/container_detail.html new file mode 100644 index 0000000..f096a16 --- /dev/null +++ b/templates/container_detail.html @@ -0,0 +1,319 @@ + + + + + + + Container: {{ container_name }} - DokkuStatus + + + + + + + + +
+
+

[ CONTAINER: {{ container_name }} ]

+ ← BACK TO LOGS +
+ + {% if container.error %} +
+
[ERROR]
+
+
{{ container.error }}
+
+
+ {% else %} + + +
+
[BASIC INFO]
+
+
+
Container ID
+
{{ container.id }}
+ +
Image
+
{{ container.image }}
+ +
Status
+
+ {{ container.state.status | upper }} +
+ +
Started At
+
{{ container.state.started_at }}
+ +
Working Directory
+
{{ container.working_dir or "—" }}
+ +
IP Address
+
{{ container.ip_address or "—" }}
+ +
Networks
+
{{ container.networks | join(", ") or "—" }}
+
+
+
+ + +
+
[ENVIRONMENT VARIABLES]
+
+ {% if container.env %} +
+ {% for env in container.env %} +
+ {% set parts = env.split('=', 1) %} +
{{ parts[0] }}
+
{{ parts[1] if parts|length > 1 else "" }}
+
+ {% endfor %} +
+ {% else %} +
No environment variables
+ {% endif %} +
+
+ + +
+
[PORT MAPPINGS]
+
+ {% if container.ports %} +
+ {% for port, mappings in container.ports.items() %} +
+ {{ port }} + {% if mappings %} + → + {% for mapping in mappings %} + {{ mapping.HostIp or "0.0.0.0" }}:{{ mapping.HostPort }} + {% endfor %} + {% else %} + (not mapped) + {% endif %} +
+ {% endfor %} +
+ {% else %} +
No port mappings
+ {% endif %} +
+
+ + +
+
[VOLUMES & MOUNTS]
+
+ {% if container.mounts %} +
+ {% for mount in container.mounts %} +
+
+ [{{ mount.type | upper }}] + + {% if mount.rw %}[RW]{% else %}[RO]{% endif %} + +
+
+ {{ mount.source }} → {{ mount.destination }} +
+
+ {% endfor %} +
+ {% else %} +
No mounts
+ {% endif %} +
+
+ + +
+
[COMMAND & ENTRYPOINT]
+
+
+
Entrypoint
+
{{ container.entrypoint | join(" ") or "—" }}
+ +
Command
+
{{ container.cmd | join(" ") or "—" }}
+
+
+
+ + +
+
[RESTART POLICY]
+
+
+
Name
+
{{ container.restart_policy.Name or "no" }}
+ +
Max Retry Count
+
{{ container.restart_policy.MaximumRetryCount or "0" }}
+
+
+
+ + {% endif %} +
+ + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index e02245d..994a55f 100644 --- a/templates/index.html +++ b/templates/index.html @@ -362,11 +362,11 @@ SOURCE | - + - LOGS + ADMIN diff --git a/templates/logs.html b/templates/logs.html index 2a37ea8..72dc199 100644 --- a/templates/logs.html +++ b/templates/logs.html @@ -211,7 +211,11 @@
[{{ r.app }}]
- +
+ [DETAILS] + +