(() => { if (window.AppFactoryOperationsLive) { return; } const state = { socket: null, reconnectTimer: null, reconnectDelay: 1500, }; const escapeHtml = (value) => String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); const statusBadge = (status) => { const value = String(status || ""); const normalized = value.toLowerCase(); let className = "pill pill-muted"; if (["success", "ok", "succeeded", "done", "deployed", "completed"].includes(normalized)) { className = "pill pill-success"; } else if (["queued", "running", "pending", "in_progress", "starting", "cancelled_requested"].includes(normalized)) { className = "pill pill-warning"; } else if (["failed", "failure", "error", "cancelled", "canceled"].includes(normalized)) { className = "pill pill-danger"; } return `${escapeHtml(value)}`; }; const workerBadge = (online) => online ? 'ONLINE' : 'OFFLINE'; const setText = (selector, value) => { document.querySelectorAll(selector).forEach((el) => { el.textContent = value ?? 0; }); }; const renderJobs = (jobs) => { const rows = (jobs || []).map((job) => ` #${escapeHtml(job.id)} ${statusBadge(job.status)} ${escapeHtml(job.type)} ${escapeHtml(job.target_type)}: ${escapeHtml(job.target_id)} ${escapeHtml(job.source)} ${escapeHtml(job.created_at)} `).join(""); return rows || 'Zatím nejsou evidované žádné joby.'; }; const renderWorkers = (workers) => { const rows = (workers || []).map((worker) => { const currentJob = worker.current_job_id === null || worker.current_job_id === undefined ? "" : `#${escapeHtml(worker.current_job_id)}`; return ` ${escapeHtml(worker.id)} ${workerBadge(Boolean(worker.online))}
${escapeHtml(worker.status)} ${escapeHtml(worker.last_seen_at)} ${currentJob} ${escapeHtml(worker.started_at)} Detail `; }).join(""); return rows || 'Zatím nejsou evidovaní žádní workeři.'; }; const renderDeployments = (deployments) => { const rows = (deployments || []).map((deployment) => ` #${escapeHtml(deployment.id)} ${escapeHtml(deployment.app_id)} ${escapeHtml(deployment.kind)} ${statusBadge(deployment.status)} ${escapeHtml(deployment.trigger_source)} ${escapeHtml(deployment.started_at)} ${escapeHtml(deployment.finished_at)} `).join(""); return rows || 'Zatím nejsou evidovaná žádná nasazení.'; }; const renderAudit = (events) => { const rows = (events || []).map((event) => ` ${escapeHtml(event.created_at)} ${escapeHtml(event.username)} ${escapeHtml(event.action)} ${escapeHtml(event.target_type)} ${escapeHtml(event.target_id)} ${escapeHtml(event.source)} `).join(""); return rows || 'Zatím nejsou evidované žádné auditní události.'; }; const renderSnapshot = (snapshot) => { setText("[data-live-count='jobs.queued']", snapshot.jobs?.queued); setText("[data-live-count='jobs.running']", snapshot.jobs?.running); setText("[data-live-count='jobs.failed_24h']", snapshot.jobs?.failed_24h); setText("[data-live-count='jobs.success_24h']", snapshot.jobs?.success_24h); setText("[data-live-count='workers.online']", snapshot.workers?.online); setText("[data-live-count='workers.offline']", snapshot.workers?.offline); setText("[data-live-count='workers.total']", (snapshot.workers?.online || 0) + (snapshot.workers?.offline || 0)); setText("[data-live-count='deployments.running']", snapshot.deployments?.running); setText("[data-live-count='deployments.failed_24h']", snapshot.deployments?.failed_24h); setText("[data-live-count='deployments.success_24h']", snapshot.deployments?.success_24h); document.querySelectorAll("[data-live-table='jobs.recent']").forEach((el) => { el.innerHTML = renderJobs(snapshot.jobs?.recent); }); document.querySelectorAll("[data-live-table='workers.recent']").forEach((el) => { el.innerHTML = renderWorkers(snapshot.workers?.recent); }); document.querySelectorAll("[data-live-table='deployments.recent']").forEach((el) => { el.innerHTML = renderDeployments(snapshot.deployments?.recent); }); document.querySelectorAll("[data-live-table='audit.recent']").forEach((el) => { el.innerHTML = renderAudit(snapshot.audit?.recent); }); (snapshot.workers?.recent || []).forEach((worker) => { const row = Array.from(document.querySelectorAll("tr[data-worker-id]")) .find((item) => item.dataset.workerId === String(worker.id || "")); if (!row) { return; } const currentJob = worker.current_job_id === null || worker.current_job_id === undefined ? "" : `#${escapeHtml(worker.current_job_id)}`; const statusCell = row.querySelector("[data-worker-field='status']"); const lastSeenCell = row.querySelector("[data-worker-field='last_seen_at']"); const currentJobCell = row.querySelector("[data-worker-field='current_job']"); if (statusCell) statusCell.innerHTML = `${workerBadge(Boolean(worker.online))}
${escapeHtml(worker.status)}`; if (lastSeenCell) lastSeenCell.textContent = worker.last_seen_at || ""; if (currentJobCell) currentJobCell.innerHTML = currentJob; }); }; const connect = () => { clearTimeout(state.reconnectTimer); const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; state.socket = new WebSocket(`${protocol}//${window.location.host}/portal/ws/operations`); state.socket.addEventListener("message", (event) => { const snapshot = JSON.parse(event.data); if (snapshot.type !== "operations.snapshot") { return; } window.dispatchEvent(new CustomEvent("operations:snapshot", { detail: snapshot })); renderSnapshot(snapshot); }); state.socket.addEventListener("close", () => { state.reconnectTimer = setTimeout(connect, state.reconnectDelay); }); state.socket.addEventListener("error", () => { state.socket.close(); }); }; window.AppFactoryOperationsLive = { connect }; connect(); })();