From 6b1f4c7994b6e14067c865fd6d79126a582996c1 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Thu, 9 Oct 2025 04:16:52 -0600 Subject: [PATCH] Made Activity History Updates Report Live Status While Being Watched --- .../WebUI/src/Devices/Device_Details.jsx | 47 ++++++++++++++++--- Data/Server/server.py | 46 ++++++++++++++++++ 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/Data/Server/WebUI/src/Devices/Device_Details.jsx b/Data/Server/WebUI/src/Devices/Device_Details.jsx index 9eed9e2..10fe5b1 100644 --- a/Data/Server/WebUI/src/Devices/Device_Details.jsx +++ b/Data/Server/WebUI/src/Devices/Device_Details.jsx @@ -231,10 +231,14 @@ export default function DeviceDetails({ device, onBack }) { load(); }, [device]); + const activityHostname = useMemo(() => { + return (meta?.hostname || agent?.hostname || device?.hostname || "").trim(); + }, [meta?.hostname, agent?.hostname, device?.hostname]); + const loadHistory = useCallback(async () => { - if (!device?.hostname) return; + if (!activityHostname) return; try { - const resp = await fetch(`/api/device/activity/${encodeURIComponent(device.hostname)}`); + const resp = await fetch(`/api/device/activity/${encodeURIComponent(activityHostname)}`); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const data = await resp.json(); setHistoryRows(data.history || []); @@ -242,18 +246,47 @@ export default function DeviceDetails({ device, onBack }) { console.warn("Failed to load activity history", e); setHistoryRows([]); } - }, [device]); - - + }, [activityHostname]); useEffect(() => { loadHistory(); }, [loadHistory]); + useEffect(() => { + const socket = typeof window !== "undefined" ? window.BorealisSocket : null; + if (!socket || !activityHostname) return undefined; + + let refreshTimer = null; + const normalizedHost = activityHostname.toLowerCase(); + const scheduleRefresh = (delay = 200) => { + if (refreshTimer) clearTimeout(refreshTimer); + refreshTimer = setTimeout(() => { + refreshTimer = null; + loadHistory(); + }, delay); + }; + + const handleActivityChanged = (payload = {}) => { + const payloadHost = String(payload?.hostname || "").trim().toLowerCase(); + if (!payloadHost) return; + if (payloadHost === normalizedHost) { + const delay = payload?.change === "updated" ? 150 : 0; + scheduleRefresh(delay); + } + }; + + socket.on("device_activity_changed", handleActivityChanged); + + return () => { + if (refreshTimer) clearTimeout(refreshTimer); + socket.off("device_activity_changed", handleActivityChanged); + }; + }, [activityHostname, loadHistory]); + // No explicit live recap tab; recaps are recorded into Activity History const clearHistory = async () => { - if (!device?.hostname) return; + if (!activityHostname) return; try { - const resp = await fetch(`/api/device/activity/${encodeURIComponent(device.hostname)}`, { method: "DELETE" }); + const resp = await fetch(`/api/device/activity/${encodeURIComponent(activityHostname)}`, { method: "DELETE" }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); setHistoryRows([]); } catch (e) { diff --git a/Data/Server/server.py b/Data/Server/server.py index 6a424eb..ac6b5cf 100644 --- a/Data/Server/server.py +++ b/Data/Server/server.py @@ -4743,6 +4743,15 @@ def scripts_quick_run(): } # Broadcast to all connected clients; no broadcast kw in python-socketio v5 socketio.emit("quick_job_run", payload) + try: + socketio.emit("device_activity_changed", { + "hostname": str(host), + "activity_id": job_id, + "change": "created", + "source": "quick_job", + }) + except Exception: + pass results.append({"hostname": host, "job_id": job_id, "status": "Running"}) return jsonify({"results": results}) @@ -4821,6 +4830,13 @@ def ansible_quick_run(): try: _ansible_log_server(f"[quick_run] emit ansible_playbook_run host='{host}' run_id={run_id} job_id={job_id} path={rel_path}") socketio.emit("ansible_playbook_run", payload) + if job_id: + socketio.emit("device_activity_changed", { + "hostname": str(host), + "activity_id": job_id, + "change": "created", + "source": "ansible", + }) except Exception as ex: _ansible_log_server(f"[quick_run] emit failed host='{host}' run_id={run_id} err={ex}") results.append({"hostname": host, "run_id": run_id, "status": "Queued", "activity_job_id": job_id}) @@ -4904,6 +4920,7 @@ def handle_quick_job_result(data): status = (data.get("status") or "").strip() or "Failed" stdout = data.get("stdout") or "" stderr = data.get("stderr") or "" + broadcast_payload = None try: conn = _db_conn() cur = conn.cursor() @@ -4912,9 +4929,30 @@ def handle_quick_job_result(data): (status, stdout, stderr, job_id), ) conn.commit() + try: + cur.execute( + "SELECT id, hostname, status FROM activity_history WHERE id=?", + (job_id,), + ) + row = cur.fetchone() + if row and (row[1] or "").strip(): + broadcast_payload = { + "activity_id": row[0], + "hostname": row[1], + "status": row[2] or status, + "change": "updated", + "source": "quick_job", + } + except Exception: + pass conn.close() except Exception as e: print(f"[ERROR] quick_job_result DB update failed for job {job_id}: {e}") + if broadcast_payload: + try: + socketio.emit("device_activity_changed", broadcast_payload) + except Exception: + pass # --------------------------------------------- @@ -5287,6 +5325,14 @@ def api_ansible_recap_report(): "updated_at": row[15], } socketio.emit("ansible_recap_update", payload) + if payload.get("activity_job_id"): + socketio.emit("device_activity_changed", { + "hostname": payload.get("hostname") or "", + "activity_id": payload.get("activity_job_id"), + "status": payload.get("status") or "", + "change": "updated", + "source": "ansible", + }) except Exception: pass