function setMode(m) { mode = m; $$("#modeTabs .mode-tab").forEach(t => t.classList.toggle("active", t.dataset.mode === m)); $("#listPane").classList.toggle("hidden", m !== "list"); $("#monPane").classList.toggle("hidden", m !== "monitor"); if (m === "list") { stopStream(); } if (m === "monitor" && MON.id) { refreshMonSel(); startMonitor(); } } async function refreshMonSel() { try { const d = await GET("/campaigns/list?page_size=100"); $("#monSel").innerHTML = (d.campaigns || []).map(c => ``).join(""); } catch {} $("#monSel").onchange = () => { MON.id = +$("#monSel").value; startMonitor(); }; } function stopStream() { if (MON.es) { MON.es.close(); MON.es = null; } if (MON.timer) { clearInterval(MON.timer); MON.timer = null; } $("#liveInd").classList.add("hidden"); } async function startMonitor() { if (!MON.id) return; stopStream(); const body = $("#monBody"); body.innerHTML = `
`; await drawProgress(); // initial paint via REST await drawRecipients(); drawControls(); // SSE MON.es = new EventSource(`/campaigns/${MON.id}/stream`); MON.es.addEventListener("connected", () => $("#liveInd").classList.remove("hidden")); MON.es.addEventListener("progress", ev => { try { paintProgress(JSON.parse(ev.data)); } catch {} }); MON.es.addEventListener("done", ev => { try { paintProgress(JSON.parse(ev.data)); } catch {} stopStream(); toast(`Campaign finished — status: ${(JSON.parse(ev.data) || {}).status || "?"}`, "info", 6000); drawControls(); }); MON.es.onerror = () => { $("#liveInd").classList.add("hidden"); }; // Live poll: keep per-recipient table + stats fresh while running/paused.