feat: include device hostname in monitoring page and enhance event view toggle functionality
All checks were successful
CI / test (push) Successful in 7s

This commit is contained in:
Space-Banane
2026-05-27 21:29:33 +02:00
parent 595375e1a7
commit 52e09ce3b0
2 changed files with 77 additions and 8 deletions

View File

@@ -1,7 +1,9 @@
from __future__ import annotations
from html import escape
def monitoring_page_html() -> str:
def monitoring_page_html(device_hostname: str = "") -> str:
host_suffix = f" ({escape(device_hostname)})" if device_hostname else ""
return """<!doctype html>
<html lang="en">
<head>
@@ -14,7 +16,7 @@ def monitoring_page_html() -> str:
<div class="max-w-7xl mx-auto p-4 md:p-8 space-y-6">
<header class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div>
<h1 class="text-2xl md:text-3xl font-bold tracking-tight">ScreenJob Monitor</h1>
<h1 class="text-2xl md:text-3xl font-bold tracking-tight">ScreenJob Monitor<span class="text-slate-400 text-base md:text-lg font-medium">__MONITOR_HOST__</span></h1>
<p class="text-slate-400 text-sm">Read-only monitoring for active and historical tasks.</p>
</div>
<div class="flex flex-col md:flex-row gap-2 md:items-center">
@@ -41,7 +43,14 @@ def monitoring_page_html() -> str:
<div class="bg-slate-950 border border-slate-800 rounded p-2">
<img id="latestVisual" alt="Latest visual update" class="max-h-[24vh] w-full object-contain rounded" />
</div>
<h3 class="font-semibold text-sm">Live Events</h3>
<div class="flex items-center justify-between">
<h3 class="font-semibold text-sm">Live Events</h3>
<label for="eventsViewToggle" class="flex items-center gap-2 text-xs text-slate-300 cursor-pointer select-none">
<span>Raw</span>
<input id="eventsViewToggle" type="checkbox" class="accent-cyan-400 h-4 w-4" />
<span>Beautiful</span>
</label>
</div>
<div id="events" class="bg-slate-950 border border-slate-800 rounded p-3 text-xs overflow-auto max-h-[36vh] space-y-1"></div>
</div>
</section>
@@ -56,13 +65,15 @@ def monitoring_page_html() -> str:
const eventsEl = document.getElementById("events");
const statsEl = document.getElementById("stats");
const latestVisualEl = document.getElementById("latestVisual");
const eventsViewToggle = document.getElementById("eventsViewToggle");
const state = {
token: localStorage.getItem("screenjob_token") || "",
jobs: [],
selectedJobId: null,
ws: null,
wsReconnectTimer: null
wsReconnectTimer: null,
eventsViewMode: localStorage.getItem("screenjob_events_view_mode") === "beautiful" ? "beautiful" : "raw"
};
const manuallyClosedSockets = new WeakSet();
tokenInput.value = state.token;
@@ -123,10 +134,56 @@ def monitoring_page_html() -> str:
function pushEventLine(obj) {
if (!obj || !obj.job_id || !obj.event_type) return;
const line = document.createElement("div");
line.className = "border-b border-slate-800 pb-1";
const ts = obj.ts || "-";
const step = (obj.step ?? "-");
line.textContent = `[${ts}] ${obj.job_id} step=${step} ${obj.event_type} ${JSON.stringify(obj.payload || {})}`;
if (state.eventsViewMode === "raw") {
line.className = "border-b border-slate-800 pb-1";
line.textContent = `[${ts}] ${obj.job_id} step=${step} ${obj.event_type} ${JSON.stringify(obj.payload || {})}`;
} else {
const typeColors = {
info: "bg-sky-900/50 text-sky-200 border border-sky-800",
warning: "bg-amber-900/40 text-amber-200 border border-amber-800",
error: "bg-rose-900/40 text-rose-200 border border-rose-800",
visual_update: "bg-emerald-900/40 text-emerald-200 border border-emerald-800",
tool_call: "bg-violet-900/40 text-violet-200 border border-violet-800",
tool_result: "bg-indigo-900/40 text-indigo-200 border border-indigo-800"
};
const dt = new Date(ts);
const tsText = Number.isNaN(dt.getTime()) ? ts : dt.toLocaleString();
const payload = obj.payload || {};
line.className = "rounded-lg border border-slate-800 bg-slate-900/80 p-2 space-y-2";
const header = document.createElement("div");
header.className = "flex flex-wrap items-center gap-2";
const typePill = document.createElement("span");
typePill.className = `px-2 py-0.5 rounded text-[10px] font-semibold ${typeColors[obj.event_type] || "bg-slate-800 text-slate-200 border border-slate-700"}`;
typePill.textContent = obj.event_type;
const stepPill = document.createElement("span");
stepPill.className = "px-2 py-0.5 rounded text-[10px] bg-slate-800 text-slate-300 border border-slate-700";
stepPill.textContent = `step ${step}`;
const tsSpan = document.createElement("span");
tsSpan.className = "text-[10px] text-slate-400";
tsSpan.textContent = tsText;
header.appendChild(typePill);
header.appendChild(stepPill);
header.appendChild(tsSpan);
const jobLine = document.createElement("div");
jobLine.className = "text-[11px] text-slate-300 font-medium";
jobLine.textContent = obj.job_id;
const body = document.createElement("pre");
body.className = "bg-slate-950 border border-slate-800 rounded p-2 text-[11px] text-slate-200 overflow-auto";
body.textContent = JSON.stringify(payload, null, 2);
line.appendChild(header);
line.appendChild(jobLine);
line.appendChild(body);
}
eventsEl.prepend(line);
while (eventsEl.childNodes.length > 400) {
eventsEl.removeChild(eventsEl.lastChild);
@@ -231,10 +288,20 @@ def monitoring_page_html() -> str:
connectWs();
}
function syncEventsViewToggle() {
eventsViewToggle.checked = state.eventsViewMode === "beautiful";
}
saveTokenBtn.addEventListener("click", () => connect().catch((err) => alert(err.message)));
refreshBtn.addEventListener("click", () => fullRefresh().catch((err) => alert(err.message)));
eventsViewToggle.addEventListener("change", () => {
state.eventsViewMode = eventsViewToggle.checked ? "beautiful" : "raw";
localStorage.setItem("screenjob_events_view_mode", state.eventsViewMode);
refreshJobDetail().catch((err) => alert(err.message));
});
syncEventsViewToggle();
if (state.token) connect().catch(() => {});
</script>
</body>
</html>
"""
""".replace("__MONITOR_HOST__", host_suffix)