window.pageBoot = async (me) => { const state = { accounts: [] }; const fmtDT = window.fmtDT || (s => s || "—"); function statusBadge(a){ if (a.cooldown_until && new Date(a.cooldown_until) > new Date()) return `cooldown`; if (a.authorized) return `authorized`; const m = {expired:"b-amber", revoked:"b-red", pending:"b-gray", error:"b-red"}; return `${esc(a.status)}`; } function render(){ const q = $("#accSearch").value.trim().toLowerCase(); const rows = state.accounts.filter(a => !q || a.email.toLowerCase().includes(q)); $("#accCount").textContent = String(state.accounts.length); $("#accEmpty").style.display = state.accounts.length ? "none" : ""; $("#accTbl").style.display = state.accounts.length ? "" : "none"; $("#accBody").innerHTML = rows.map(a => `
${esc(a.email)}
refresh token: ${a.has_refresh_token ? "stored" : "none"} ${a.token_expires_at ? " · expires " + esc(fmtDT(a.token_expires_at)) : ""}
${statusBadge(a)} ${esc((a.sending_method||"api").toUpperCase())} ${a.daily_limit} / ${a.campaign_limit} ${a.sends_today ?? 0} sent ${a.last_success_at ? esc(fmtDT(a.last_success_at)) : "—"} ${a.error_count ? `${a.error_count}
${esc(a.last_error||"")}
` : "0"}
`).join(""); } async function load(){ const d = await GET("/gmail/accounts"); state.accounts = d.accounts || []; render(); } $("#accSearch").addEventListener("input", render); // Full absolute redirect URI (copy-pasteable into Google Cloud Console) const cfgHint = $("#cfgRedirect"); if (cfgHint && !cfgHint.dataset.full) cfgHint.textContent = location.origin + "/auth/google/callback"; // ---- Connect flow (real Google OAuth start) ---- $("#btnConnect").addEventListener("click", async () => { const btn = $("#btnConnect"); btn.disabled = true; btn.textContent = "Starting…"; try { const d = await POST("/gmail/accounts/connect"); const url = d.authorization_url || d.url || d.auth_url; if (!url) throw new Error("No authorization URL returned"); // Navigate the current tab to Google's consent screen (popup blockers // kill window.open() after an await, so we use a direct navigation). sessionStorage.setItem("oauth_start_url", url); location.href = url; } catch (e) { toast("Connect failed: " + e.message, "err"); btn.disabled = false; btn.textContent = "Connect Gmail Account"; } }); // ---- Import OAuth client config ---- $("#btnImportCfg").addEventListener("click", () => { $("#cfgCard").style.display = $("#cfgCard").style.display === "none" ? "" : "none"; }); $("#cfgForm").addEventListener("submit", async (e) => { e.preventDefault(); const f = $("#cfgFile").files[0]; if (!f) return; const fd = new FormData(); fd.append("file", f); try { const d = await api("/gmail/accounts/import-config", { method: "POST", body: fd }); $("#cfgRedirect").textContent = d.redirect_uri; toast(`Configuration imported (project: ${d.project_id}). You can now connect accounts.`, "ok"); $("#cfgForm").reset(); } catch (err) { toast("Import failed: " + err.message, "err"); } }); // ---- Clear all ---- $("#btnClearAll").addEventListener("click", () => { confirmedAction("Clear ALL Gmail accounts? This revokes tokens at Google and deletes all stored credentials for every connected account. Audit logs are preserved.", async () => { const d = await POST("/gmail/accounts/clear-all"); const revoked = d.revoked_at_google ? `token(s) revoked at Google: ${d.revoked_at_google}` : "no live tokens to revoke at Google"; toast(`Cleared ${d.removed} account(s) — ${revoked}.`, "ok"); await load(); }); }); // ---- Row actions ---- $("#accBody").addEventListener("click", async (e) => { const b = e.target.closest("button"); if (!b) return; const id = b.dataset.test || b.dataset.edit || b.dataset.reauth || b.dataset.disc; if (b.dataset.test) { b.disabled = true; b.textContent = "Testing…"; try { const d = await POST(`/gmail/accounts/${id}/test`); if (d.ok) toast("Connection verified: " + (d.detail || "Gmail profile OK"), "ok"); else toast("Test failed: " + (d.detail || d.error || "unknown"), "err"); await load(); } catch (err) { toast("Test failed: " + (err.message || "request failed"), "err"); } finally { b.disabled = false; b.textContent = "Test"; } } if (b.dataset.reauth) { try { const d = await POST(`/gmail/accounts/${id}/reauthorize`); if (d.authorization_url) { sessionStorage.setItem("oauth_start_url", d.authorization_url); location.href = d.authorization_url; } else { toast("Reauthorize failed: no authorization URL returned", "err"); } } catch (err) { toast("Reauthorize failed: " + err.message, "err"); } } if (b.dataset.edit) { const a = state.accounts.find(x => x.id == id); const dm = el("select", {class:"inp"}, ...[["api","Gmail API (recommended)"],["smtp","SMTP XOAUTH2"]].map(([v,l]) => el("option", {value:v, selected:(a.sending_method||"api")===v}, l))); const dl = el("input", {class:"inp", type:"number", value:a.daily_limit, min:1, max:2000}); const cl = el("input", {class:"inp", type:"number", value:a.campaign_limit, min:1, max:2000}); openModal("Account Limits — " + a.email, el("div", {class:"form"}, el("label", {}, "Sending method"), dm, el("label", {}, "Daily send limit (conservative recommended)"), dl, el("label", {}, "Per-campaign limit"), cl), { buttons: [{label:"Save", kind:"btn", onClick: async (close) => { const payload = { sending_method: dm.value, daily_limit: +dl.value, campaign_limit: +cl.value }; await api(`/gmail/accounts/${id}`, { method: "PATCH", body: payload }); toast("Limits updated", "ok"); close(); await load(); }}] }); } if (b.dataset.disc) { confirmedAction(`Disconnect this account? The stored OAuth token will be revoked at Google and deleted locally.`, async () => { const d = await api(`/gmail/accounts/${id}`, { method: "DELETE" }); const revoked = d.revoked_at_google ? "token revoked at Google" : "no live token to revoke at Google"; toast(`Disconnected — ${revoked}. Credentials deleted locally.`, "ok"); await load(); }); } }); // ---- OAuth return handling: show result + refresh list ---- const qParams = new URLSearchParams(location.search); const connectedEmail = qParams.get("connected"); const oauthError = qParams.get("error"); if (connectedEmail || oauthError) { history.replaceState(null, "", "/gmail-accounts"); } if (connectedEmail) { toast(`Gmail account connected: ${connectedEmail}`, "ok", 8000); } else if (oauthError) { toast(`Google authorization failed: ${oauthError}`, "err", 8000); } await load(); };