feat(settings/smtp): bouton Test SMTP dans Settings (envoi mail HTML pro a destinataire)

- Endpoint POST /settings/smtp/test (Form 'recipient', defaut kalid.moutaouakil@gmail.com)
  envoie un mail HTML pro confirmant que SMTP fonctionne (header bleu degrade,
  metadonnees envoyeur/date/destinataire en card)
- UI Settings > SMTP: champ destinataire test pre-rempli + bouton 'Envoyer test'
  AJAX sans reload, status vert/rouge en dessous
- Reuse send_html_mail du mail_service
This commit is contained in:
Pierre & Lumière 2026-05-07 22:04:15 +02:00
parent 98d0ad0a3d
commit bfd91634bb
2 changed files with 95 additions and 0 deletions

View File

@ -238,6 +238,61 @@ async def settings_save(request: Request, section: str, db=Depends(get_db)):
return templates.TemplateResponse("settings.html", ctx) return templates.TemplateResponse("settings.html", ctx)
@router.post("/settings/smtp/test")
async def settings_smtp_test(request: Request, db=Depends(get_db),
recipient: str = Form("kalid.moutaouakil@gmail.com")):
"""Envoie un mail test à `recipient` pour valider la config SMTP."""
from fastapi.responses import JSONResponse
user = get_current_user(request)
if not user:
return JSONResponse({"ok": False, "msg": "Non authentifié"}, status_code=401)
perms = get_user_perms(db, user)
if not can_edit(perms, "settings"):
return JSONResponse({"ok": False, "msg": "Permission refusée"}, status_code=403)
to = (recipient or "").strip()
if not to:
return JSONResponse({"ok": False, "msg": "Destinataire vide"}, status_code=400)
sender = user.get("sub") or user.get("username") or "PatchCenter"
from datetime import datetime as _dt
now_str = _dt.now().strftime("%d/%m/%Y %H:%M:%S")
subject = f"[PatchCenter] Test SMTP — {now_str}"
html = f"""<!DOCTYPE html><html><body style="font-family:'Segoe UI',Arial,sans-serif;background:#f3f4f6;padding:24px;">
<table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background:#fff;border-radius:8px;margin:0 auto;box-shadow:0 1px 3px rgba(0,0,0,.08);">
<tr><td style="background:linear-gradient(90deg,#1e3a8a 0%,#1e40af 100%);padding:20px 28px;color:#fff;border-radius:8px 8px 0 0;">
<div style="font-size:11px;letter-spacing:0.15em;text-transform:uppercase;opacity:0.85;">SANEF SecOps · PatchCenter</div>
<h1 style="margin:6px 0 0;font-size:20px;font-weight:600;"> Test SMTP réussi</h1>
</td></tr>
<tr><td style="padding:24px 28px;color:#1f2937;font-size:14px;line-height:1.6;">
<p>Bonjour,</p>
<p>Ce mail confirme que la configuration SMTP de <strong>PatchCenter</strong> fonctionne correctement.</p>
<table role="presentation" width="100%" cellspacing="0" cellpadding="0"
style="border-left:4px solid #2563eb;background:#eff6ff;border-radius:4px;margin:16px 0;">
<tr><td style="padding:14px 18px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="font-size:13px;">
<tr><td style="padding:3px 0;color:#6b7280;font-weight:600;width:40%;">Envoyé par :</td>
<td style="padding:3px 0;color:#1f2937;">{sender}</td></tr>
<tr><td style="padding:3px 0;color:#6b7280;font-weight:600;">Date d'envoi :</td>
<td style="padding:3px 0;color:#1f2937;">{now_str}</td></tr>
<tr><td style="padding:3px 0;color:#6b7280;font-weight:600;">Destinataire :</td>
<td style="padding:3px 0;color:#1f2937;">{to}</td></tr>
</table>
</td></tr>
</table>
<p style="color:#6b7280;font-size:13px;">Tu peux maintenant utiliser le bouton "Prévenance PCT" sur la page d'import patching.</p>
<p style="margin:24px 0 0;color:#6b7280;font-size:13px;">Cordialement,<br>L'équipe SecOps SANEF</p>
</td></tr>
<tr><td style="background:#f9fafb;padding:12px 28px;border-top:1px solid #e5e7eb;color:#9ca3af;font-size:11px;border-radius:0 0 8px 8px;">
Mail de test généré par <strong>PatchCenter</strong> via <code>/settings/smtp/test</code>.
</td></tr>
</table></body></html>"""
from ..services.mail_service import send_html_mail
res = send_html_mail(db, to=[to], subject=subject, html=html)
return JSONResponse(res)
@router.post("/settings/ldap/test") @router.post("/settings/ldap/test")
async def settings_ldap_test(request: Request, db=Depends(get_db)): async def settings_ldap_test(request: Request, db=Depends(get_db)):
"""Teste la connexion LDAP avec le compte de bind.""" """Teste la connexion LDAP avec le compte de bind."""

View File

@ -709,6 +709,46 @@
</p> </p>
{% if editable.smtp %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %} {% if editable.smtp %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
</form> </form>
{% if editable.smtp %}
<div class="mt-4 pt-3 border-t border-cyber-border">
<h4 class="text-xs text-cyber-accent font-bold uppercase mb-2">Test envoi SMTP</h4>
<div class="flex gap-2 items-end">
<div class="flex-1">
<label class="text-xs text-gray-500">Destinataire test</label>
<input type="text" id="smtp-test-recipient" value="kalid.moutaouakil@gmail.com" class="w-full">
</div>
<button type="button" id="smtp-test-btn" class="btn-sm bg-cyber-blue/20 text-cyber-blue px-4 py-2 text-xs whitespace-nowrap">📤 Envoyer test</button>
</div>
<div id="smtp-test-status" class="text-xs mt-2"></div>
</div>
<script>
(function(){
const btn = document.getElementById('smtp-test-btn');
const inp = document.getElementById('smtp-test-recipient');
const out = document.getElementById('smtp-test-status');
if (!btn) return;
btn.addEventListener('click', async () => {
const to = (inp.value || '').trim();
if (!to) { alert('Renseigne un destinataire.'); return; }
btn.disabled = true; btn._txt = btn.textContent; btn.textContent = '⏳ Envoi…';
out.innerHTML = '<span class="text-cyber-yellow">⏳ Envoi en cours…</span>';
try {
const fd = new FormData(); fd.append('recipient', to);
const r = await fetch('/settings/smtp/test', {method:'POST', credentials:'same-origin', body:fd});
const j = await r.json();
out.innerHTML = j.ok
? `<span class="text-cyber-green">✅ ${j.msg || 'Envoyé'}</span>`
: `<span class="text-cyber-red">❌ ${j.msg || 'Échec'}</span>`;
} catch (e) {
out.innerHTML = `<span class="text-cyber-red">❌ Erreur réseau : ${e}</span>`;
} finally {
btn.disabled = false; btn.textContent = btn._txt || '📤 Envoyer test';
}
});
})();
</script>
{% endif %}
</div> </div>
</div> </div>
{% endif %} {% endif %}