video_press/static/js/modules/settings.js

260 lines
8.8 KiB
JavaScript

/**
* settings.js
* -----------
* SMTP email settings modal.
*
* Loads saved settings from the server on open, lets the user edit and
* save them, and sends a test email to verify the configuration works.
*
* Exports
* -------
* initSettings() — wire up all listeners; call once at startup
* smtpIsConfigured() — returns true if the server has smtp_host saved
*/
import { announce } from './state.js';
// ─── DOM refs (local to this module) ─────────────────────────────────────────
const $ = id => document.getElementById(id);
const modal = $('settings-modal');
const openBtn = $('settings-btn');
const openFromHint = $('open-settings-from-hint');
const closeBtn = $('close-settings');
const cancelBtn = $('settings-cancel');
const saveBtn = $('settings-save');
const saveStatus = $('settings-save-status');
const hostInput = $('smtp-host');
const portInput = $('smtp-port');
const securitySel = $('smtp-security');
const fromInput = $('smtp-from');
const userInput = $('smtp-user');
const passwordInput = $('smtp-password');
const passwordHint = $('smtp-password-hint');
const togglePwBtn = $('toggle-password');
const testToInput = $('smtp-test-to');
const testBtn = $('smtp-test-btn');
const testResult = $('smtp-test-result');
// ─── Module-level state ───────────────────────────────────────────────────────
let _configured = false; // whether smtp_host is set on the server
// ─── Public API ───────────────────────────────────────────────────────────────
/**
* Returns true if the server has an SMTP host configured.
* Used by compress.js to warn the user before they start a job with
* notifications enabled but no SMTP server set up.
*/
export function smtpIsConfigured() {
return _configured;
}
/**
* Attach all event listeners for the settings modal.
* Call once during app initialisation.
*/
export function initSettings() {
openBtn.addEventListener('click', openSettings);
if (openFromHint) openFromHint.addEventListener('click', openSettings);
const openFromWarn = document.getElementById('open-settings-from-warn');
if (openFromWarn) openFromWarn.addEventListener('click', openSettings);
closeBtn.addEventListener('click', closeSettings);
cancelBtn.addEventListener('click', closeSettings);
modal.addEventListener('click', e => { if (e.target === modal) closeSettings(); });
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && !modal.hidden) closeSettings();
});
saveBtn.addEventListener('click', saveSettings);
testBtn.addEventListener('click', sendTestEmail);
// Password show/hide toggle
togglePwBtn.addEventListener('click', () => {
const isHidden = passwordInput.type === 'password';
passwordInput.type = isHidden ? 'text' : 'password';
togglePwBtn.setAttribute('aria-label', isHidden ? 'Hide password' : 'Show password');
});
// Auto-fill port when security mode changes
securitySel.addEventListener('change', () => {
const presets = { tls: '587', ssl: '465', none: '25' };
portInput.value = presets[securitySel.value] || portInput.value;
});
// Load current config silently at startup so smtpIsConfigured() works
_fetchConfig(false);
}
// ─── Open / close ─────────────────────────────────────────────────────────────
async function openSettings() {
modal.hidden = false;
document.body.style.overflow = 'hidden';
clearStatus();
await _fetchConfig(true);
closeBtn.focus();
announce('SMTP settings panel opened');
}
function closeSettings() {
modal.hidden = true;
document.body.style.overflow = '';
openBtn.focus();
announce('SMTP settings panel closed');
}
// ─── Load settings from server ────────────────────────────────────────────────
async function _fetchConfig(populateForm) {
try {
const resp = await fetch('/api/settings/smtp');
if (!resp.ok) return;
const cfg = await resp.json();
_configured = Boolean(cfg.host);
if (!populateForm) return;
hostInput.value = cfg.host || '';
portInput.value = cfg.port || '587';
fromInput.value = cfg.from_addr || '';
userInput.value = cfg.user || '';
passwordInput.value = ''; // never pre-fill passwords
// Select the right security option
const opt = securitySel.querySelector(`option[value="${cfg.security || 'tls'}"]`);
if (opt) opt.selected = true;
passwordHint.textContent = cfg.password_set
? 'A password is saved. Enter a new value to replace it, or leave blank to keep it.'
: '';
} catch {
// Silently ignore — server may not be reachable during init
}
}
// ─── Save ────────────────────────────────────────────────────────────────────
async function saveSettings() {
const host = hostInput.value.trim();
const port = portInput.value.trim();
const security = securitySel.value;
const from = fromInput.value.trim();
const user = userInput.value.trim();
const password = passwordInput.value; // not trimmed — passwords may have spaces
if (!host) {
showStatus('SMTP server host is required.', 'fail');
hostInput.focus();
return;
}
if (!port || isNaN(Number(port))) {
showStatus('A valid port number is required.', 'fail');
portInput.focus();
return;
}
if (!from || !from.includes('@')) {
showStatus('A valid From address is required.', 'fail');
fromInput.focus();
return;
}
saveBtn.disabled = true;
saveBtn.textContent = 'Saving…';
clearStatus();
try {
const body = { host, port, security, from_addr: from, user };
if (password) body.password = password;
const resp = await fetch('/api/settings/smtp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const data = await resp.json();
if (!resp.ok) {
showStatus(`Error: ${data.error}`, 'fail');
return;
}
_configured = Boolean(data.config?.host);
passwordInput.value = '';
passwordHint.textContent =
'Password saved. Enter a new value to replace it, or leave blank to keep it.';
showStatus('Settings saved successfully.', 'ok');
announce('SMTP settings saved.');
} catch (err) {
showStatus(`Network error: ${err.message}`, 'fail');
} finally {
saveBtn.disabled = false;
saveBtn.textContent = 'Save Settings';
}
}
// ─── Test email ───────────────────────────────────────────────────────────────
async function sendTestEmail() {
const to = testToInput.value.trim();
if (!to || !to.includes('@')) {
setTestResult('Please enter a valid recipient address.', 'fail');
testToInput.focus();
return;
}
testBtn.disabled = true;
testBtn.textContent = 'Sending…';
setTestResult('Sending test email…', '');
try {
const resp = await fetch('/api/settings/smtp/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ to }),
});
const data = await resp.json();
if (data.ok) {
setTestResult(`${data.message}`, 'ok');
announce(`Test email sent to ${to}.`);
} else {
setTestResult(`${data.message}`, 'fail');
announce(`Test email failed: ${data.message}`);
}
} catch (err) {
setTestResult(`Network error: ${err.message}`, 'fail');
} finally {
testBtn.disabled = false;
testBtn.textContent = 'Send Test';
}
}
// ─── Helpers ─────────────────────────────────────────────────────────────────
function showStatus(msg, type) {
saveStatus.textContent = msg;
saveStatus.className = `settings-save-status ${type}`;
}
function clearStatus() {
saveStatus.textContent = '';
saveStatus.className = 'settings-save-status';
setTestResult('', '');
}
function setTestResult(msg, type) {
testResult.textContent = msg;
testResult.style.color =
type === 'ok' ? 'var(--text-success)'
: type === 'fail' ? 'var(--text-danger)'
: 'var(--text-muted)';
}