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 = `
${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.
| Email | Status | Usage | Updated | Actions |
${state.mailboxes.map(m => `
| ${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()} |
|
`).join('') || '| 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
| Time | Actor | Action | Target |
${state.audit.map(a => `| ${new Date(a.created_at).toLocaleString()} | ${esc(a.actor_email)} | ${esc(a.action)} | ${esc(a.target_id)} |
`).join('')}
`);
}
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();