feat(smtp): add a precious SMTP test for admin only
This commit is contained in:
@@ -61,6 +61,9 @@
|
||||
<button class="admin-tab" id="tab-btn-storage">
|
||||
<i class="fas fa-database"></i> <span data-i18n="admin.tab_storage">Storage</span>
|
||||
</button>
|
||||
<button class="admin-tab" id="tab-btn-smtp">
|
||||
<i class="fas fa-envelope"></i> <span data-i18n="admin.tab_smtp">SMTP</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="tab-dashboard" class="tab-content active">
|
||||
@@ -609,6 +612,70 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════ SMTP tab ════════ -->
|
||||
<div id="tab-smtp" class="tab-content">
|
||||
<div class="admin-card">
|
||||
<h2>
|
||||
<i class="fas fa-envelope"></i> <span data-i18n="admin.smtp_title">Outbound Email (SMTP)</span>
|
||||
</h2>
|
||||
<p class="muted" data-i18n="admin.smtp_intro">
|
||||
SMTP is configured exclusively via environment variables (OXICLOUD_SMTP_*). The values below are read from the running server — to change them, edit the environment and restart OxiCloud.
|
||||
</p>
|
||||
|
||||
<table class="smtp-info-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th data-i18n="admin.smtp_enabled_label">Status</th>
|
||||
<td id="smtp-enabled">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>OXICLOUD_SMTP_HOST</th>
|
||||
<td id="smtp-host">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>OXICLOUD_SMTP_PORT</th>
|
||||
<td id="smtp-port">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>OXICLOUD_SMTP_TLS</th>
|
||||
<td id="smtp-tls">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>OXICLOUD_SMTP_FROM</th>
|
||||
<td id="smtp-from">—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>OXICLOUD_SMTP_USER</th>
|
||||
<td id="smtp-user-state">—</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 class="mt-14">
|
||||
<i class="fas fa-paper-plane"></i> <span data-i18n="admin.smtp_test_title">Send a test email</span>
|
||||
</h3>
|
||||
<p class="muted" data-i18n="admin.smtp_test_intro">
|
||||
Sends a hardcoded diagnostic message to the recipient below and reports the SMTP server's response so you can correlate it with your relay logs.
|
||||
</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="smtp-test-to" data-i18n="admin.smtp_test_to">Recipient address</label>
|
||||
<input
|
||||
id="smtp-test-to"
|
||||
type="email"
|
||||
placeholder="alice@example.com"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button id="btn-smtp-test" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane"></i> <span data-i18n="admin.smtp_send_test">Send test email</span>
|
||||
</button>
|
||||
|
||||
<div id="smtp-test-result" class="alert" style="display:none; margin-top:14px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -970,6 +970,45 @@ details[open] summary {
|
||||
color: var(--color-text-heading);
|
||||
}
|
||||
|
||||
/* Simple two-column "key: value" table used by the SMTP admin panel.
|
||||
Designed for the SMTP-info read-only view where the values
|
||||
(hostnames, full mailboxes, status strings) are too long for the
|
||||
centered stat-card layout above. */
|
||||
.smtp-info-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 24px;
|
||||
background: var(--color-bg-hover);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.smtp-info-table th,
|
||||
.smtp-info-table td {
|
||||
padding: 10px 14px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
vertical-align: middle;
|
||||
word-break: break-word;
|
||||
}
|
||||
.smtp-info-table tr:last-child th,
|
||||
.smtp-info-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.smtp-info-table th {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-secondary);
|
||||
width: 220px;
|
||||
white-space: nowrap;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
.smtp-info-table td {
|
||||
color: var(--color-text-heading);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.storage-backend-selector {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
@@ -154,6 +154,7 @@ function switchTab(name, el) {
|
||||
if (name === 'users') loadUsers();
|
||||
if (name === 'dashboard') loadDashboard();
|
||||
if (name === 'storage') loadStorage();
|
||||
if (name === 'smtp') loadSmtp();
|
||||
}
|
||||
|
||||
async function loadDashboard() {
|
||||
@@ -1158,6 +1159,110 @@ function showAccessDenied() {
|
||||
showElement('access-denied');
|
||||
}
|
||||
|
||||
/* ── SMTP tab ──────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Fetch the runtime SMTP info and render the read-only status grid.
|
||||
* Configuration is sourced exclusively from `OXICLOUD_SMTP_*` env vars;
|
||||
* this view is purely diagnostic — no save path exists.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function loadSmtp() {
|
||||
try {
|
||||
const resp = await fetch(`${API}/admin/smtp/info`, {
|
||||
headers: headers(),
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
if (!resp.ok) return;
|
||||
/** @type {{enabled: boolean, host: string, port: number, tls: string, from: string, user_state: string}} */
|
||||
const info = await resp.json();
|
||||
|
||||
const enabledEl = document.getElementById('smtp-enabled');
|
||||
if (enabledEl) {
|
||||
enabledEl.textContent = info.enabled ? i18n.t('admin.smtp_enabled') || 'Enabled' : i18n.t('admin.smtp_disabled') || 'Disabled (host unset)';
|
||||
enabledEl.style.color = info.enabled ? 'var(--success)' : 'var(--text-muted)';
|
||||
}
|
||||
const setText = (/** @type {string} */ id, /** @type {string} */ value) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = value || '—';
|
||||
};
|
||||
setText('smtp-host', info.host);
|
||||
setText('smtp-port', String(info.port));
|
||||
setText('smtp-tls', info.tls);
|
||||
setText('smtp-from', info.from);
|
||||
setText('smtp-user-state', info.user_state);
|
||||
} catch (e) {
|
||||
console.error('Failed to load SMTP info', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a diagnostic test email through the configured SMTP relay.
|
||||
* Backend always responds with 200 carrying `{success, code, message,
|
||||
* error}` — SMTP-level failures are operational data, not HTTP errors.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function sendSmtpTest() {
|
||||
const input = /** @type {HTMLInputElement | null} */ (document.getElementById('smtp-test-to'));
|
||||
const resultEl = document.getElementById('smtp-test-result');
|
||||
const btn = /** @type {HTMLButtonElement | null} */ (document.getElementById('btn-smtp-test'));
|
||||
if (!input || !resultEl) return;
|
||||
|
||||
const to = input.value.trim();
|
||||
if (!to) {
|
||||
resultEl.className = 'alert alert-error';
|
||||
resultEl.style.display = 'block';
|
||||
resultEl.textContent = i18n.t('admin.smtp_test_missing_to') || 'Enter a recipient address.';
|
||||
return;
|
||||
}
|
||||
|
||||
if (btn) btn.disabled = true;
|
||||
resultEl.className = 'alert alert-info';
|
||||
resultEl.style.display = 'block';
|
||||
resultEl.textContent = i18n.t('admin.smtp_sending') || 'Sending…';
|
||||
|
||||
try {
|
||||
const resp = await fetch(`${API}/admin/smtp/test`, {
|
||||
method: 'POST',
|
||||
headers: headers(),
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({ to })
|
||||
});
|
||||
if (resp.status === 503) {
|
||||
resultEl.className = 'alert alert-error';
|
||||
resultEl.textContent = i18n.t('admin.smtp_not_configured') || 'SMTP is not configured on this server.';
|
||||
return;
|
||||
}
|
||||
if (!resp.ok) {
|
||||
resultEl.className = 'alert alert-error';
|
||||
resultEl.textContent = `HTTP ${resp.status}: ${await resp.text()}`;
|
||||
return;
|
||||
}
|
||||
/** @type {{success: boolean, code?: number, message?: string, error?: string}} */
|
||||
const data = await resp.json();
|
||||
if (data.success) {
|
||||
resultEl.className = 'alert alert-success';
|
||||
const codeLabel = i18n.t('admin.smtp_server_code') || 'Server replied';
|
||||
resultEl.innerHTML =
|
||||
`<strong>${escapeHtml(i18n.t('admin.smtp_sent') || 'Test email sent.')}</strong><br>` +
|
||||
`${escapeHtml(codeLabel)}: <code>${data.code ?? ''} ${escapeHtml(data.message ?? '')}</code>`;
|
||||
} else {
|
||||
resultEl.className = 'alert alert-error';
|
||||
const failLabel = i18n.t('admin.smtp_send_failed') || 'Send failed.';
|
||||
resultEl.innerHTML = `<strong>${escapeHtml(failLabel)}</strong><br>` + `<code>${escapeHtml(data.error ?? 'unknown error')}</code>`;
|
||||
}
|
||||
} catch (e) {
|
||||
resultEl.className = 'alert alert-error';
|
||||
resultEl.textContent = i18n.t('admin.error_network', {
|
||||
message: /** @type {Error} */ (e).message
|
||||
});
|
||||
} finally {
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Apply i18n when translations load / change ── */
|
||||
document.addEventListener('translationsLoaded', () => {
|
||||
i18n.translatePage();
|
||||
@@ -1186,6 +1291,11 @@ document.getElementById('tab-btn-oidc').addEventListener('click', function () {
|
||||
document.getElementById('tab-btn-storage').addEventListener('click', function () {
|
||||
switchTab('storage', this);
|
||||
});
|
||||
document.getElementById('tab-btn-smtp').addEventListener('click', function () {
|
||||
switchTab('smtp', this);
|
||||
});
|
||||
|
||||
document.getElementById('btn-smtp-test').addEventListener('click', sendSmtpTest);
|
||||
|
||||
document.getElementById('ds-registration').addEventListener('change', function () {
|
||||
toggleRegistration(/** @type {HTMLInputElement} */ (this).checked);
|
||||
|
||||
+17
-1
@@ -649,7 +649,23 @@
|
||||
"migration_verify_passed": "Verification passed",
|
||||
"migration_verify_failed": "Verification failed",
|
||||
"migration_failed_blobs": "failed blobs",
|
||||
"testing": "Testing…"
|
||||
"testing": "Testing…",
|
||||
"tab_smtp": "SMTP",
|
||||
"smtp_title": "Outbound Email (SMTP)",
|
||||
"smtp_intro": "SMTP is configured exclusively via environment variables (OXICLOUD_SMTP_*). The values below are read from the running server — to change them, edit the environment and restart OxiCloud.",
|
||||
"smtp_enabled_label": "Status",
|
||||
"smtp_enabled": "Enabled",
|
||||
"smtp_disabled": "Disabled (host unset)",
|
||||
"smtp_test_title": "Send a test email",
|
||||
"smtp_test_intro": "Sends a hardcoded diagnostic message to the recipient below and reports the SMTP server's response so you can correlate it with your relay logs.",
|
||||
"smtp_test_to": "Recipient address",
|
||||
"smtp_send_test": "Send test email",
|
||||
"smtp_sending": "Sending…",
|
||||
"smtp_sent": "Test email sent.",
|
||||
"smtp_send_failed": "Send failed.",
|
||||
"smtp_server_code": "Server replied",
|
||||
"smtp_test_missing_to": "Enter a recipient address.",
|
||||
"smtp_not_configured": "SMTP is not configured on this server."
|
||||
},
|
||||
"profile": {
|
||||
"page_title": "Profile",
|
||||
|
||||
+17
-1
@@ -649,7 +649,23 @@
|
||||
"migration_verify_passed": "Vérification réussie",
|
||||
"migration_verify_failed": "Échec de la vérification",
|
||||
"migration_failed_blobs": "Blobs échoués",
|
||||
"testing": "Test en cours..."
|
||||
"testing": "Test en cours...",
|
||||
"tab_smtp": "SMTP",
|
||||
"smtp_title": "E-mail sortant (SMTP)",
|
||||
"smtp_intro": "Le SMTP est configuré exclusivement via les variables d'environnement (OXICLOUD_SMTP_*). Les valeurs ci-dessous proviennent du serveur en cours d'exécution — pour les modifier, éditez l'environnement et redémarrez OxiCloud.",
|
||||
"smtp_enabled_label": "État",
|
||||
"smtp_enabled": "Activé",
|
||||
"smtp_disabled": "Désactivé (hôte non défini)",
|
||||
"smtp_test_title": "Envoyer un e-mail de test",
|
||||
"smtp_test_intro": "Envoie un message de diagnostic au destinataire ci-dessous et affiche la réponse du serveur SMTP afin que vous puissiez la corréler avec les journaux de votre relais.",
|
||||
"smtp_test_to": "Adresse du destinataire",
|
||||
"smtp_send_test": "Envoyer l'e-mail de test",
|
||||
"smtp_sending": "Envoi…",
|
||||
"smtp_sent": "E-mail de test envoyé.",
|
||||
"smtp_send_failed": "Échec de l'envoi.",
|
||||
"smtp_server_code": "Le serveur a répondu",
|
||||
"smtp_test_missing_to": "Veuillez saisir une adresse de destinataire.",
|
||||
"smtp_not_configured": "Le SMTP n'est pas configuré sur ce serveur."
|
||||
},
|
||||
"profile": {
|
||||
"page_title": "Profil",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// OxiCloud Service Worker
|
||||
// FIXME: generate cache name according build ?
|
||||
const CACHE_NAME = 'oxicloud-cache-v23';
|
||||
const CACHE_NAME = 'oxicloud-cache-v24';
|
||||
|
||||
// Only cache static assets — NOT HTML files.
|
||||
// HTML files are served network-first so browsers always get the latest
|
||||
|
||||
Reference in New Issue
Block a user