window.pageBoot = async (me) => { let tab = "attempts"; let campaigns = []; let st = { attempts: 1, events: 1, audit: 1, dlq: 1 }; const PS = 50; const OUTCOMES = ["", "submitted", "accepted", "temp_failed", "perm_failed", "skipped"]; const OUT_BADGE = { submitted: "b-blue", accepted: "b-green", temp_failed: "b-amber", perm_failed: "b-red", skipped: "b-gray", sent: "b-green", suppressed: "b-violet", bounced: "b-red" }; const sel = (id, opts, val) => `"; async function loadCampaigns() { try { const d = await GET("/campaigns/list?page_size=100"); campaigns = d.campaigns || []; } catch { campaigns = []; } } function renderFilters() { const f = $("#filters"); const campOpts = [{ v: "", t: "All campaigns" }].concat( campaigns.map(c => ({ v: String(c.id), t: `#${c.id} — ${c.name}`.slice(0, 60) }))); if (tab === "attempts") { f.innerHTML = sel("fCamp", campOpts, st._camp || "") + sel("fOutcome", OUTCOMES.map(o => ({ v: o, t: o ? o.replace("_", " ") : "All outcomes" })), st._out || "") + `` + ``; $("#fGo").onclick = applyFilters; $("#fSearch").onkeydown = e => { if (e.key === "Enter") applyFilters(); }; } else if (tab === "events") { f.innerHTML = sel("fCamp", campOpts, st._camp || "") + sel("fEvType", ["", "queued", "submitted", "accepted", "failed", "retry_scheduled", "suppressed", "unsubscribed", "bounced", "dlq"].map(o => ({ v: o, t: o ? o.replace("_", " ") : "All event types" })), st._ev || "") + ``; $("#fGo").onclick = applyFilters; } else if (tab === "audit") { f.innerHTML = sel("fAction", ["", "auth.login", "auth.register", "campaign.create", "campaign.start", "campaign.pause", "campaign.resume", "campaign.cancel", "recipients.import", "recipients.manual_add", "subjects.import", "templates.upload", "attachments.upload", "phones.import", "suppression.add", "gmail.connect", "gmail.disconnect", "gmail.import_config", "settings.save_batch", "settings.reset"].map(o => ({ v: o, t: o ? o.replace(/\./g, " › ") : "All actions" })), st._act || "") + ``; $("#fGo").onclick = applyFilters; } else { f.innerHTML = `Jobs that exhausted all retries and were moved to the dead-letter queue (dlq:send).`; } } function applyFilters() { if (tab === "attempts") { st._camp = $("#fCamp").value; st._out = $("#fOutcome").value; st._q = $("#fSearch").value.trim(); } else if (tab === "events") { st._camp = $("#fCamp").value; st._ev = $("#fEvType").value; } else if (tab === "audit") { st._act = $("#fAction").value; } st[tab] = 1; load(); } function pagerHTML(total, page, key) { const pages = Math.max(1, Math.ceil(total / PS)); if (pages <= 1) return ""; return ` Page ${page} of ${pages} · ${total} records `; } function bindPager(key) { $$("#pager [data-pg]").forEach(b => b.onclick = () => { st[key] = parseInt(b.dataset.pg, 10); load(); }); } function badge(v) { return `${esc(v || "—")}`; } async function load() { const box = $("#listing"); box.innerHTML = `
`; try { if (tab === "attempts") { const p = new URLSearchParams({ campaign_id: st._camp || 0, outcome: st._out || "", search: st._q || "", page: st.attempts, page_size: PS }); const d = await GET("/logs/list?" + p); const rows = d.logs || []; box.innerHTML = rows.length ? `| # | Time | Outcome | Attempt | Error class | Error / provider response | Message ID | ||
|---|---|---|---|---|---|---|---|---|
| ${r.id} | ${fmtDT(r.created_at)} | ${esc(r.email || "")} | ${badge(r.outcome)} | ${r.attempt_number} | ${r.error_class ? `${esc(r.error_class)}` : "—"} | ${esc((r.error_message || r.provider_response || "").slice(0, 120) || "—")} | ${r.message_id ? esc(r.message_id.slice(0, 18)) + "…" : "—"} |
No send attempts match these filters yet.
| # | Time | Campaign | Event | Detail | Message ID | |
|---|---|---|---|---|---|---|
| ${r.id} | ${fmtDT(r.created_at)} | #${r.campaign_id} | ${esc(r.email || "")} | ${badge(r.event_type)} | ${esc(r.detail || "—")} | ${r.message_id ? esc(r.message_id.slice(0, 16)) + "…" : "—"} |
No email events yet. Events appear as campaign messages are queued, submitted and accepted.
| # | Time | Action | Entity | Detail | IP |
|---|---|---|---|---|---|
| ${r.id} | ${fmtDT(r.created_at)} | ${esc(r.action)} | ${esc(r.entity || "—")}${r.entity_id ? ` #${r.entity_id}` : ""} | ${esc(JSON.stringify(r.detail || {}).slice(0, 140))} | ${esc(r.ip_address || "—")} |
No audit entries yet. Administrative actions are recorded here.
${esc(JSON.stringify(it, null, 2).slice(0, 3000))}
Dead-letter queue is empty. No jobs have exhausted their retries.