// Copyright © 2026 Olaf Kolkman // SPDX-License-Identifier: GPL-3.0-or-later const setupForm = document.querySelector('#setup-form'); const testMailButton = document.querySelector('#test-mail-button'); const completeSetupButton = document.querySelector('#complete-setup-button'); const setupStatus = document.querySelector('#setup-status'); const setupTimer = document.querySelector('#setup-timer'); let nextAllowedAt = null; let timerHandle = null; function updateTimer() { if (timerHandle) window.clearTimeout(timerHandle); if (!nextAllowedAt) { setupTimer.textContent = ''; testMailButton.disabled = !setupForm.dataset.saved; return; } const seconds = Math.max(0, Math.ceil((nextAllowedAt - Date.now()) / 1000)); if (seconds === 0) { nextAllowedAt = null; updateTimer(); return; } const minutes = Math.floor(seconds / 60); setupTimer.textContent = `Next test mail available in ${minutes ? `${minutes}m ` : ''}${seconds % 60}s.`; testMailButton.disabled = true; timerHandle = window.setTimeout(updateTimer, 1000); } function applyStatus(result) { setupForm.dataset.saved = result.pending ? 'true' : ''; testMailButton.disabled = !result.pending; const smtpDefaults = result.smtp_defaults || {}; for (const [name, value] of Object.entries(smtpDefaults)) { const field = setupForm.elements[name]; if (!field || field.value || field.type === 'checkbox') continue; field.value = value; } if (typeof smtpDefaults.smtp_use_tls === 'boolean') { setupForm.elements.smtp_use_tls.checked = smtpDefaults.smtp_use_tls; } if (result.next_allowed_at) nextAllowedAt = Date.parse(result.next_allowed_at); updateTimer(); } async function loadSetupStatus() { const response = await fetch('/api/setup/status'); if (!response.ok) return; applyStatus(await response.json()); } setupForm.addEventListener('submit', async (event) => { event.preventDefault(); const form = event.currentTarget; const values = Object.fromEntries(new FormData(form)); values.smtp_port = Number(values.smtp_port); values.smtp_use_tls = form.elements.smtp_use_tls.checked; try { const response = await fetch('/api/setup/configuration', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(values), }); const result = await response.json(); if (!response.ok) throw new Error(result.detail || 'Setup failed'); setupStatus.textContent = result.message; setupStatus.style.color = '#94e2d5'; form.dataset.saved = 'true'; testMailButton.disabled = false; } catch (error) { setupStatus.textContent = error.message; setupStatus.style.color = '#f38ba8'; } }); testMailButton.addEventListener('click', async () => { testMailButton.disabled = true; try { const response = await fetch('/api/setup/test-mail', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({email: setupForm.elements.email.value}), }); const result = await response.json(); if (!response.ok) { if (response.status === 429 && response.headers.get('Retry-After')) { nextAllowedAt = Date.now() + Number(response.headers.get('Retry-After')) * 1000; updateTimer(); } throw new Error(result.detail || 'Test mail failed'); } setupStatus.textContent = `${result.message} ${result.sends_remaining} sends remaining.`; setupStatus.style.color = '#94e2d5'; if (result.next_allowed_at) nextAllowedAt = Date.parse(result.next_allowed_at); updateTimer(); completeSetupButton.hidden = false; } catch (error) { setupStatus.textContent = error.message; setupStatus.style.color = '#f38ba8'; if (!nextAllowedAt) testMailButton.disabled = false; } }); completeSetupButton.addEventListener('click', async () => { const response = await fetch('/api/setup/complete', {method: 'POST'}); const result = await response.json(); if (!response.ok) { setupStatus.textContent = result.detail || 'Could not complete setup.'; setupStatus.style.color = '#f38ba8'; return; } setupStatus.style.color = '#94e2d5'; setupStatus.textContent = ''; setupStatus.append(document.createTextNode(`${result.message} `)); const homeLink = document.createElement('a'); homeLink.href = '/'; homeLink.textContent = 'Home'; setupStatus.append(homeLink); setupForm.replaceChildren(setupStatus); }); loadSetupStatus();