mail setup

This commit is contained in:
2026-02-24 01:15:02 +01:00
parent e563617da4
commit da44c2c9a5
6 changed files with 576 additions and 3 deletions

View File

@@ -24,9 +24,11 @@ export function initEditor() {
  const sendSubject  = document.getElementById('send_subject');
  const sendInfo     = document.getElementById('send_template_info');
  const btnCancelSend= document.getElementById('btn-cancel-send');
  const btnSendNow   = document.getElementById('btn-send-now');
const btnSendNow   = document.getElementById('btn-send-now');
const sendSender   = document.getElementById('send_sender');
const sendSenderHint = document.getElementById('send_sender_hint');
const sendSmtpProfile = document.getElementById('send_smtp_profile');
const sendSmtpProfileHint = document.getElementById('send_smtp_profile_hint');
const prevFrame    = document.getElementById('previewFrame');
const btnPrevClose = document.getElementById('btn-close-preview');
const unsavedDialog = document.getElementById('unsavedDialog');
@@ -46,6 +48,8 @@ export function initEditor() {
let reqToken = 0; // steigender Token pro Öffnen -> ignoriert verspätete Events
let senderOptions = [];
let senderLoadPromise = null;
let smtpProfileOptions = [];
let smtpProfileLoadPromise = null;
let currentEditorType = 'grapesjs';
let versionItems = [];
let savedSnapshot = '';
@@ -902,6 +906,24 @@ export function initEditor() {
    return senderLoadPromise;
  }
  async function loadSmtpProfileOptions(force = false) {
    if (!sendSmtpProfile) return;
    if (smtpProfileLoadPromise && !force) return smtpProfileLoadPromise;
    smtpProfileLoadPromise = apiAction('account.smtp_profiles.list', { method: 'GET' })
      .then(res => {
        smtpProfileOptions = res?.items || [];
        renderSmtpProfileOptions();
      })
      .catch(() => {
        smtpProfileOptions = [];
        renderSmtpProfileOptions();
      })
      .finally(() => {
        smtpProfileLoadPromise = null;
      });
    return smtpProfileLoadPromise;
  }
  function renderSenderOptions() {
    if (!sendSender) return;
    const previous = sendSender.value;
@@ -921,6 +943,25 @@ export function initEditor() {
    }
  }
  function renderSmtpProfileOptions() {
    if (!sendSmtpProfile) return;
    const previous = sendSmtpProfile.value;
    let html = '<option value="">Standard (System)</option>';
    smtpProfileOptions.forEach(opt => {
      const label = opt.label || opt.smtp_host || 'Profil';
      html += `<option value="${opt.id}">${escapeHtml(label)}</option>`;
    });
    sendSmtpProfile.innerHTML = html;
    if (previous && smtpProfileOptions.some(opt => String(opt.id) === previous)) {
      sendSmtpProfile.value = previous;
    } else {
      sendSmtpProfile.value = '';
    }
    if (sendSmtpProfileHint) {
      sendSmtpProfileHint.classList.toggle('hidden', smtpProfileOptions.length > 0);
    }
  }
  // ---------- Initialen HTML-Inhalt in Editor pushen (mit Token/Race-Schutz) ----------
async function pushInitialHtmlToEditor({ mode, html, snippets, ref, token, hasJson, json }) {
if (token !== reqToken) return; // veraltete Anfrage ignorieren
@@ -1266,11 +1307,13 @@ export function initEditor() {
    if (sendSubject) sendSubject.value = ctx?.subject || 'Testversand';
    if (sendTo) sendTo.value = ctx?.to || '';
    await loadSenderOptions(true);
    await loadSmtpProfileOptions(true);
    sendDlg?.showModal?.();
  }
  function closeSend(){
    sendDlg?.close?.();
    if (sendSender) sendSender.value = '';
    if (sendSmtpProfile) sendSmtpProfile.value = '';
  }
  async function doSend(ev){
@@ -1291,6 +1334,9 @@ export function initEditor() {
    if (sendSender && sendSender.value) {
      payload.sender_id = Number(sendSender.value);
    }
    if (sendSmtpProfile && sendSmtpProfile.value) {
      payload.smtp_profile_id = Number(sendSmtpProfile.value);
    }
    const r = await apiAction('templates.test_send', { method:'POST', data: payload });
    if(r?.ok){ toast("Testversand ausgelöst"); closeSend(); } else { toast("Senden fehlgeschlagen", false); }
  }

View File

@@ -7,6 +7,8 @@ const state = {
userMap: new Map(),
senders: [],
senderMap: new Map(),
smtpProfiles: [],
smtpProfileMap: new Map(),
currentTab: 'profile',
loading: false,
};
@@ -25,6 +27,8 @@ let teamTable;
let userForm;
let senderTable;
let senderForm;
let smtpProfileTable;
let smtpProfileForm;
let sectionsList;
let sectionsCreateForm;
let sectionNameInput;
@@ -82,6 +86,8 @@ export function initAccountPage() {
userForm = document.getElementById('userForm');
senderTable = document.getElementById('senderTable');
senderForm = document.getElementById('senderForm');
smtpProfileTable = document.getElementById('smtpProfileTable');
smtpProfileForm = document.getElementById('smtpProfileForm');
adminTablesAllSelect = document.getElementById('adminBridgeTablesAll');
adminTablesSelectedSelect = document.getElementById('adminBridgeTablesSelected');
adminTablesAddBtn = document.getElementById('adminBridgeTablesAdd');
@@ -105,11 +111,16 @@ export function initAccountPage() {
document.getElementById('senderFormCancel')?.addEventListener('click', () => closeSenderForm());
senderForm?.addEventListener('submit', submitSenderForm);
document.getElementById('btn-smtp-profile-add')?.addEventListener('click', () => openSmtpProfileForm());
document.getElementById('smtpProfileFormCancel')?.addEventListener('click', () => closeSmtpProfileForm());
smtpProfileForm?.addEventListener('submit', submitSmtpProfileForm);
profileForm?.addEventListener('submit', submitProfileForm);
passwordForm?.addEventListener('submit', submitPasswordForm);
settingsForm?.addEventListener('submit', submitSettingsForm);
teamTable?.addEventListener('click', handleTeamTableClick);
senderTable?.addEventListener('click', handleSenderTableClick);
smtpProfileTable?.addEventListener('click', handleSmtpProfileTableClick);
document.querySelectorAll('[data-user-tab]').forEach(btn => {
btn.addEventListener('click', () => switchTab(btn.getAttribute('data-user-tab')));
});
@@ -421,6 +432,15 @@ async function loadAccountData() {
renderSenderList();
}
}
if (smtpProfileTable) {
if (isAdmin()) {
await loadSmtpProfiles();
} else {
state.smtpProfiles = [];
state.smtpProfileMap = new Map();
renderSmtpProfileList();
}
}
reportViewDebugInfo(res);
} catch (err) {
console.error(err);
@@ -928,6 +948,144 @@ async function deleteSender(senderId) {
}
}
async function loadSmtpProfiles() {
if (!smtpProfileTable) return;
try {
const res = await apiAction('account.smtp_profiles.list', { method: 'GET' });
if (!res?.ok) throw new Error(res?.error || 'Versandprofile konnten nicht geladen werden');
state.smtpProfiles = res.items || [];
state.smtpProfileMap = new Map(state.smtpProfiles.map(item => [item.id, item]));
renderSmtpProfileList();
} catch (err) {
toast(err.message || 'Fehler beim Laden der Versandprofile', false);
}
}
function renderSmtpProfileList() {
if (!smtpProfileTable) return;
const tbody = smtpProfileTable.querySelector('tbody');
if (!tbody) return;
if (!state.smtpProfiles.length) {
tbody.innerHTML = '<tr><td colspan="5" class="text-sm text-slate-500">Keine Versandprofile vorhanden.</td></tr>';
return;
}
tbody.innerHTML = state.smtpProfiles.map(profile => {
const label = profile.label || profile.smtp_host || 'Profil';
const host = profile.smtp_host || '—';
const user = profile.smtp_user || '—';
const sender = profile.from_email || '—';
return `
<tr>
<td>${escapeHtml(label)}</td>
<td>${escapeHtml(host)}</td>
<td>${escapeHtml(user)}</td>
<td>${escapeHtml(sender)}</td>
<td class="text-right">
<button class="btn" data-smtp-action="edit" data-smtp-id="${profile.id}">Bearbeiten</button>
<button class="btn" data-smtp-action="copy" data-smtp-id="${profile.id}">Kopieren</button>
<button class="btn btn-danger" data-smtp-action="delete" data-smtp-id="${profile.id}">Löschen</button>
</td>
</tr>`;
}).join('');
}
function handleSmtpProfileTableClick(ev) {
const btn = ev.target.closest('button[data-smtp-action]');
if (!btn) return;
const id = Number(btn.getAttribute('data-smtp-id'));
const action = btn.getAttribute('data-smtp-action');
const profile = state.smtpProfileMap.get(id);
if (!profile) return;
if (action === 'edit') {
openSmtpProfileForm(profile);
} else if (action === 'copy') {
if (confirm(`Versandprofil "${profile.label || profile.smtp_host}" kopieren?`)) {
copySmtpProfile(id);
}
} else if (action === 'delete') {
if (confirm(`Versandprofil "${profile.label || profile.smtp_host}" wirklich löschen?`)) {
deleteSmtpProfile(id);
}
}
}
function openSmtpProfileForm(profile = null) {
if (!smtpProfileForm) return;
smtpProfileForm.classList.remove('hidden');
smtpProfileForm.profile_id.value = profile?.id || '';
smtpProfileForm.label.value = profile?.label || '';
smtpProfileForm.smtp_host.value = profile?.smtp_host || '';
smtpProfileForm.smtp_port.value = profile?.smtp_port ? String(profile.smtp_port) : '';
smtpProfileForm.smtp_user.value = profile?.smtp_user || '';
smtpProfileForm.smtp_pass.value = '';
smtpProfileForm.smtp_pass.placeholder = profile?.smtp_pass_set ? 'Passwort gesetzt' : '••••••••';
smtpProfileForm.smtp_secure.value = profile?.smtp_secure || '';
smtpProfileForm.from_email.value = profile?.from_email || '';
smtpProfileForm.from_name.value = profile?.from_name || '';
smtpProfileForm.reply_to.value = profile?.reply_to || '';
if (smtpProfileForm.smtp_pass_clear) smtpProfileForm.smtp_pass_clear.checked = false;
}
function closeSmtpProfileForm() {
if (!smtpProfileForm) return;
smtpProfileForm.classList.add('hidden');
smtpProfileForm.reset();
smtpProfileForm.profile_id.value = '';
}
async function submitSmtpProfileForm(ev) {
ev.preventDefault();
if (!smtpProfileForm) return;
const payload = {
profile_id: smtpProfileForm.profile_id.value ? Number(smtpProfileForm.profile_id.value) : undefined,
label: smtpProfileForm.label.value.trim(),
smtp_host: smtpProfileForm.smtp_host.value.trim(),
smtp_port: smtpProfileForm.smtp_port.value.trim(),
smtp_user: smtpProfileForm.smtp_user.value.trim(),
smtp_pass: smtpProfileForm.smtp_pass.value,
smtp_secure: smtpProfileForm.smtp_secure.value,
from_email: smtpProfileForm.from_email.value.trim(),
from_name: smtpProfileForm.from_name.value.trim(),
reply_to: smtpProfileForm.reply_to.value.trim(),
smtp_pass_clear: smtpProfileForm.smtp_pass_clear?.checked ? 1 : 0,
};
if (!payload.smtp_host) {
toast('Bitte einen SMTP-Server angeben', false);
return;
}
try {
const res = await apiAction('account.smtp_profiles.save', { method: 'POST', data: payload });
if (!res?.ok) throw new Error(res?.error || 'Speichern fehlgeschlagen');
closeSmtpProfileForm();
await loadSmtpProfiles();
toast('Versandprofil gespeichert', true);
} catch (err) {
toast(err.message || 'Speichern fehlgeschlagen', false);
}
}
async function deleteSmtpProfile(profileId) {
try {
const res = await apiAction('account.smtp_profiles.delete', { method: 'POST', data: { profile_id: profileId } });
if (!res?.ok) throw new Error(res?.error || 'Löschen fehlgeschlagen');
await loadSmtpProfiles();
toast('Versandprofil gelöscht', true);
} catch (err) {
toast(err.message || 'Löschen fehlgeschlagen', false);
}
}
async function copySmtpProfile(profileId) {
try {
const res = await apiAction('account.smtp_profiles.copy', { method: 'POST', data: { profile_id: profileId } });
if (!res?.ok) throw new Error(res?.error || 'Kopieren fehlgeschlagen');
await loadSmtpProfiles();
toast('Versandprofil kopiert', true);
} catch (err) {
toast(err.message || 'Kopieren fehlgeschlagen', false);
}
}
function escapeHtml(str) {
return String(str || '')
.replace(/&/g, '&amp;')

View File

@@ -130,6 +130,13 @@ require __DIR__ . '/../partials/structure/layout_start.php';
</select>
<p id="send_sender_hint" class="text-xs text-slate-500 mt-1 hidden">Keine individuellen Absender gefunden. Lege sie unter „Mein Konto“ an.</p>
</label>
<label class="block">
<span class="text-sm text-slate-600">Versandprofil (SMTP)</span>
<select id="send_smtp_profile" class="mt-1 w-full border rounded-lg px-3 py-2">
<option value="">Standard (System)</option>
</select>
<p id="send_smtp_profile_hint" class="text-xs text-slate-500 mt-1 hidden">Keine Versandprofile gefunden. Lege sie unter „Mein Konto“ an.</p>
</label>
<div class="flex justify-end gap-2">
<button type="button" id="btn-cancel-send" class="btn">Abbrechen</button>
<button type="submit" id="btn-send-now" class="btn">Senden</button>