diff --git a/src/ui.py b/src/ui.py index 1da1ded..df93730 100644 --- a/src/ui.py +++ b/src/ui.py @@ -61,8 +61,10 @@ def monitoring_page_html() -> str: token: localStorage.getItem("screenjob_token") || "", jobs: [], selectedJobId: null, - ws: null + ws: null, + wsReconnectTimer: null }; + const manuallyClosedSockets = new WeakSet(); tokenInput.value = state.token; function authHeaders() { @@ -119,15 +121,26 @@ 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"; - line.textContent = `[${obj.ts}] ${obj.job_id} step=${obj.step} ${obj.event_type} ${JSON.stringify(obj.payload || {})}`; + const ts = obj.ts || "-"; + const step = (obj.step ?? "-"); + line.textContent = `[${ts}] ${obj.job_id} step=${step} ${obj.event_type} ${JSON.stringify(obj.payload || {})}`; eventsEl.prepend(line); while (eventsEl.childNodes.length > 400) { eventsEl.removeChild(eventsEl.lastChild); } } + function scheduleWsReconnect() { + if (state.wsReconnectTimer || !state.token) return; + state.wsReconnectTimer = setTimeout(() => { + state.wsReconnectTimer = null; + connectWs(); + }, 1200); + } + function updateLatestVisualFromEvent(ev) { if (!ev || ev.event_type !== "visual_update") return; if (!state.selectedJobId || ev.job_id !== state.selectedJobId) return; @@ -164,16 +177,17 @@ def monitoring_page_html() -> str: } function connectWs() { - if (state.ws) { - try { state.ws.close(); } catch (_) {} - } if (!state.token) return; + if (state.ws && (state.ws.readyState === WebSocket.OPEN || state.ws.readyState === WebSocket.CONNECTING)) { + return; + } const scheme = location.protocol === "https:" ? "wss" : "ws"; const ws = new WebSocket(`${scheme}://${location.host}/ws?token=${encodeURIComponent(state.token)}`); state.ws = ws; ws.onmessage = async (event) => { try { const payload = JSON.parse(event.data); + if (!payload || payload.event_type === "connected") return; pushEventLine(payload); updateLatestVisualFromEvent(payload); if (!state.selectedJobId || payload.job_id === state.selectedJobId) { @@ -185,7 +199,14 @@ def monitoring_page_html() -> str: console.error(err); } }; - ws.onclose = () => setTimeout(connectWs, 1200); + ws.onclose = () => { + if (state.ws === ws) state.ws = null; + if (manuallyClosedSockets.has(ws)) { + manuallyClosedSockets.delete(ws); + return; + } + scheduleWsReconnect(); + }; } async function fullRefresh() { @@ -197,6 +218,15 @@ def monitoring_page_html() -> str: async function connect() { state.token = tokenInput.value.trim(); localStorage.setItem("screenjob_token", state.token); + if (state.ws) { + manuallyClosedSockets.add(state.ws); + try { state.ws.close(); } catch (_) {} + state.ws = null; + } + if (state.wsReconnectTimer) { + clearTimeout(state.wsReconnectTimer); + state.wsReconnectTimer = null; + } await fullRefresh(); connectWs(); }