const app = document.getElementById('app'); const state = { user: null, domains: [], selectedDomain: null, mailboxes: [], audit: [], message: '', error: '' }; async function api(path, options = {}) { const res = await fetch(path, { credentials: 'include', headers: { 'Content-Type': 'application/json', ...(options.headers || {}) }, ...options }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body.error || `HTTP ${res.status}`); } return res.json(); } function bytes(n) { n = Number(n || 0); if (n <= 0) return '0 B'; const u = ['B','KB','MB','GB','TB']; let i = 0; while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; } return `${n.toFixed(n >= 10 || i === 0 ? 0 : 1)} ${u[i]}`; } function esc(s) { return String(s ?? '').replace(/[&<>'"]/g, c => ({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c])); } async function init() { try { state.user = await api('/api/auth/me'); await loadDomains(true); } catch { state.user = null; } render(); } async function loadDomains(resync = false) { state.domains = await api(`/api/domains${resync ? '?resync=true' : ''}`); if (!state.selectedDomain && state.domains.length) state.selectedDomain = state.domains[0].domain; if (state.selectedDomain) await loadMailboxes(); } async function loadMailboxes() { state.mailboxes = await api(`/api/mailboxes?domain=${encodeURIComponent(state.selectedDomain)}`); } async function loadAudit() { state.audit = await api('/api/audit'); renderAuditModal(); } function render() { if (!state.user) return renderLogin(); app.innerHTML = `
MailAdmin
${esc(state.user.email)} · ${esc(state.user.role)}
${state.error ? `
${esc(state.error)}
` : ''} ${state.message ? `
${esc(state.message)}
` : ''}

Domains on this node

Domains are discovered dynamically from DMS accounts.
${state.domains.map(d => `
${esc(d.domain)}
${d.active_mailboxes || 0} inboxes · ${bytes(d.used_bytes)}
${esc(d.current_node)} ${esc(d.status)}
`).join('') || '
No domains found yet.
'}

${esc(state.selectedDomain || 'Mailboxes')}

Create/delete mailboxes, reset passwords, edit rules.
${state.mailboxes.map(m => ` `).join('') || ''}
EmailStatusUsageUpdatedActions
${esc(m.email_address)}
${esc(m.node_name)}
${esc(m.status)} ${bytes(m.used_bytes)}
${m.usage_scanned_at ? new Date(m.usage_scanned_at).toLocaleString() : 'not scanned'}
${new Date(m.updated_at).toLocaleString()}
No mailboxes for this domain.
`; document.getElementById('logoutBtn').onclick = async () => { await api('/api/auth/logout', { method:'POST' }); state.user = null; render(); }; document.getElementById('resyncBtn').onclick = guard(async () => { await api('/api/domains/resync', { method:'POST' }); await loadDomains(false); state.message = 'DMS sync completed.'; render(); }); document.getElementById('auditBtn').onclick = guard(loadAudit); document.getElementById('newMailboxBtn').onclick = renderCreateMailboxModal; document.getElementById('usageBtn').onclick = guard(async () => { await api('/api/mailboxes/usage/rescan', { method:'POST', body: JSON.stringify({ domain: state.selectedDomain }) }); await loadMailboxes(); render(); }); document.querySelectorAll('[data-domain]').forEach(el => el.onclick = guard(async () => { state.selectedDomain = el.dataset.domain; await loadMailboxes(); render(); })); document.querySelectorAll('[data-delete]').forEach(el => el.onclick = () => renderDeleteModal(el.dataset.delete)); document.querySelectorAll('[data-password]').forEach(el => el.onclick = () => renderPasswordModal(el.dataset.password)); document.querySelectorAll('[data-rules]').forEach(el => el.onclick = () => renderRulesModal(el.dataset.rules)); document.querySelectorAll('[data-blocks]').forEach(el => el.onclick = () => renderBlocklistModal(el.dataset.blocks)); } function renderLogin() { app.innerHTML = `

MailAdmin Login

${state.error ? `
${esc(state.error)}
` : ''}




`; document.getElementById('loginForm').onsubmit = guard(async e => { e.preventDefault(); const f = new FormData(e.target); state.user = await api('/api/auth/login', { method:'POST', body: JSON.stringify({ email: f.get('email'), password: f.get('password') }) }); await loadDomains(true); render(); }); } function modal(html) { const div = document.createElement('div'); div.className = 'modal-backdrop'; div.innerHTML = ``; document.body.appendChild(div); div.querySelector('[data-close]').onclick = () => div.remove(); div.onclick = e => { if (e.target === div) div.remove(); }; return div; } function renderCreateMailboxModal() { const d = modal(`

New mailbox

`); d.querySelector('#createForm').onsubmit = guard(async e => { e.preventDefault(); const f = new FormData(e.target); await api('/api/mailboxes', { method:'POST', body: JSON.stringify({ email:f.get('email'), password:f.get('password') }) }); d.remove(); await loadDomains(false); render(); }); } function renderDeleteModal(email) { const d = modal(`

Delete mailbox

Delete ${esc(email)} from DMS?

`); d.querySelector('#confirmDelete').onclick = guard(async () => { await api(`/api/mailboxes/${encodeURIComponent(email)}`, { method:'DELETE' }); d.remove(); await loadMailboxes(); render(); }); } function renderPasswordModal(email) { const d = modal(`

Reset password



`); d.querySelector('#pwForm').onsubmit = guard(async e => { e.preventDefault(); const f = new FormData(e.target); await api(`/api/mailboxes/${encodeURIComponent(email)}/password`, { method:'POST', body: JSON.stringify({ password:f.get('password') }) }); d.remove(); state.message = `Password updated for ${email}.`; render(); }); } async function renderRulesModal(email) { const rules = await api(`/api/mailboxes/${encodeURIComponent(email)}/rules`); const d = modal(`

Rules for ${esc(email)}







`); d.querySelector('#rulesForm').onsubmit = guard(async e => { e.preventDefault(); const f = new FormData(e.target); await api(`/api/mailboxes/${encodeURIComponent(email)}/rules`, { method:'PUT', body: JSON.stringify({ ooo_active: !!f.get('ooo_active'), ooo_message:f.get('ooo_message'), forwards:String(f.get('forwards')||'').split(/\n|,/).map(x=>x.trim()).filter(Boolean) }) }); d.remove(); }); } async function renderBlocklistModal(email) { const block = await api(`/api/mailboxes/${encodeURIComponent(email)}/blocklist`); const d = modal(`

Blocklist for ${esc(email)}



`); d.querySelector('#blockForm').onsubmit = guard(async e => { e.preventDefault(); const f = new FormData(e.target); await api(`/api/mailboxes/${encodeURIComponent(email)}/blocklist`, { method:'PUT', body: JSON.stringify({ blocked_patterns:String(f.get('blocked_patterns')||'').split('\n').map(x=>x.trim()).filter(Boolean) }) }); d.remove(); }); } function renderAuditModal() { modal(`

Audit Log

${state.audit.map(a => ``).join('')}
TimeActorActionTarget
${new Date(a.created_at).toLocaleString()}${esc(a.actor_email)}${esc(a.action)}${esc(a.target_id)}
`); } function guard(fn) { return async function(...args) { try { state.error=''; state.message=''; await fn.apply(this,args); } catch(e) { state.error = e.message; render(); } }; } init();