Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
508 lines
21 KiB
Python
508 lines
21 KiB
Python
"""
|
|
Admin Email Component - E-Mail Template Management
|
|
"""
|
|
|
|
def get_admin_email_css() -> str:
|
|
"""CSS für E-Mail Templates (inkludiert in admin_panel.css)"""
|
|
return ""
|
|
|
|
|
|
def get_admin_email_html() -> str:
|
|
"""HTML für E-Mail Templates (inkludiert in admin_panel.html)"""
|
|
return ""
|
|
|
|
|
|
def get_admin_email_js() -> str:
|
|
"""JavaScript für E-Mail Template Management zurückgeben"""
|
|
return """
|
|
let emailTemplates = [];
|
|
let emailTemplateVersions = [];
|
|
let currentEmailTemplateId = null;
|
|
let currentEmailVersionId = null;
|
|
|
|
// E-Mail-Template-Typen mit deutschen Namen
|
|
const emailTypeNames = {
|
|
'welcome': 'Willkommens-E-Mail',
|
|
'email_verification': 'E-Mail-Verifizierung',
|
|
'password_reset': 'Passwort zurücksetzen',
|
|
'password_changed': 'Passwort geändert',
|
|
'2fa_enabled': '2FA aktiviert',
|
|
'2fa_disabled': '2FA deaktiviert',
|
|
'new_device_login': 'Neues Gerät Login',
|
|
'suspicious_activity': 'Verdächtige Aktivität',
|
|
'account_locked': 'Account gesperrt',
|
|
'account_unlocked': 'Account entsperrt',
|
|
'deletion_requested': 'Löschung angefordert',
|
|
'deletion_confirmed': 'Löschung bestätigt',
|
|
'data_export_ready': 'Datenexport bereit',
|
|
'email_changed': 'E-Mail geändert',
|
|
'new_version_published': 'Neue Version veröffentlicht',
|
|
'consent_reminder': 'Consent Erinnerung',
|
|
'consent_deadline_warning': 'Consent Frist Warnung',
|
|
'account_suspended': 'Account suspendiert'
|
|
};
|
|
|
|
// Load E-Mail Templates when tab is clicked
|
|
document.querySelector('.admin-tab[data-tab="emails"]')?.addEventListener('click', loadEmailTemplates);
|
|
|
|
async function loadEmailTemplates() {
|
|
try {
|
|
const res = await fetch('/api/consent/admin/email-templates');
|
|
if (!res.ok) throw new Error('Fehler beim Laden der Templates');
|
|
const data = await res.json();
|
|
emailTemplates = data.templates || [];
|
|
populateEmailTemplateSelect();
|
|
} catch (e) {
|
|
console.error('Error loading email templates:', e);
|
|
showToast('Fehler beim Laden der E-Mail-Templates', 'error');
|
|
}
|
|
}
|
|
|
|
function populateEmailTemplateSelect() {
|
|
const select = document.getElementById('email-template-select');
|
|
select.innerHTML = '<option value="">-- E-Mail-Vorlage auswählen --</option>';
|
|
|
|
emailTemplates.forEach(template => {
|
|
const opt = document.createElement('option');
|
|
opt.value = template.id;
|
|
opt.textContent = emailTypeNames[template.type] || template.name;
|
|
select.appendChild(opt);
|
|
});
|
|
}
|
|
|
|
async function loadEmailTemplateVersions() {
|
|
const select = document.getElementById('email-template-select');
|
|
const templateId = select.value;
|
|
const newVersionBtn = document.getElementById('btn-new-email-version');
|
|
const infoCard = document.getElementById('email-template-info');
|
|
const container = document.getElementById('email-version-table-container');
|
|
|
|
if (!templateId) {
|
|
newVersionBtn.disabled = true;
|
|
infoCard.style.display = 'none';
|
|
container.innerHTML = '<div class="admin-empty">Wählen Sie eine E-Mail-Vorlage aus, um deren Versionen anzuzeigen.</div>';
|
|
currentEmailTemplateId = null;
|
|
return;
|
|
}
|
|
|
|
currentEmailTemplateId = templateId;
|
|
newVersionBtn.disabled = false;
|
|
|
|
// Finde das Template
|
|
const template = emailTemplates.find(t => t.id === templateId);
|
|
if (template) {
|
|
infoCard.style.display = 'block';
|
|
document.getElementById('email-template-name').textContent = emailTypeNames[template.type] || template.name;
|
|
document.getElementById('email-template-description').textContent = template.description || 'Keine Beschreibung';
|
|
document.getElementById('email-template-type-badge').textContent = template.type;
|
|
|
|
// Variablen anzeigen (wird aus dem Default-Inhalt ermittelt)
|
|
try {
|
|
const defaultRes = await fetch(`/api/consent/admin/email-templates/default/${template.type}`);
|
|
if (defaultRes.ok) {
|
|
const defaultData = await defaultRes.json();
|
|
const variables = extractVariables(defaultData.body_html || '');
|
|
document.getElementById('email-template-variables').textContent = variables.join(', ') || 'Keine';
|
|
}
|
|
} catch (e) {
|
|
document.getElementById('email-template-variables').textContent = '-';
|
|
}
|
|
}
|
|
|
|
// Lade Versionen
|
|
container.innerHTML = '<div class="admin-loading">Lade Versionen...</div>';
|
|
try {
|
|
const res = await fetch(`/api/consent/admin/email-templates/${templateId}/versions`);
|
|
if (!res.ok) throw new Error('Fehler beim Laden');
|
|
const data = await res.json();
|
|
emailTemplateVersions = data.versions || [];
|
|
renderEmailVersionsTable();
|
|
} catch (e) {
|
|
container.innerHTML = '<div class="admin-empty">Fehler beim Laden der Versionen.</div>';
|
|
}
|
|
}
|
|
|
|
function extractVariables(content) {
|
|
const matches = content.match(/\\{\\{([^}]+)\\}\\}/g) || [];
|
|
return [...new Set(matches.map(m => m.replace(/[{}]/g, '')))];
|
|
}
|
|
|
|
function renderEmailVersionsTable() {
|
|
const container = document.getElementById('email-version-table-container');
|
|
|
|
if (emailTemplateVersions.length === 0) {
|
|
container.innerHTML = '<div class="admin-empty">Keine Versionen vorhanden. Erstellen Sie eine neue Version.</div>';
|
|
return;
|
|
}
|
|
|
|
const statusColors = {
|
|
'draft': 'draft',
|
|
'review': 'review',
|
|
'approved': 'approved',
|
|
'published': 'published',
|
|
'archived': 'archived'
|
|
};
|
|
|
|
const statusNames = {
|
|
'draft': 'Entwurf',
|
|
'review': 'In Prüfung',
|
|
'approved': 'Genehmigt',
|
|
'published': 'Veröffentlicht',
|
|
'archived': 'Archiviert'
|
|
};
|
|
|
|
container.innerHTML = `
|
|
<table class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Version</th>
|
|
<th>Sprache</th>
|
|
<th>Betreff</th>
|
|
<th>Status</th>
|
|
<th>Aktualisiert</th>
|
|
<th style="text-align: right;">Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${emailTemplateVersions.map(v => `
|
|
<tr>
|
|
<td><strong>${v.version}</strong></td>
|
|
<td>${v.language === 'de' ? '🇩🇪 DE' : '🇬🇧 EN'}</td>
|
|
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${v.subject}</td>
|
|
<td><span class="admin-badge badge-${statusColors[v.status]}">${statusNames[v.status] || v.status}</span></td>
|
|
<td>${new Date(v.updated_at).toLocaleDateString('de-DE')}</td>
|
|
<td style="text-align: right;">
|
|
<button class="btn btn-ghost btn-xs" onclick="previewEmailVersionById('${v.id}')" title="Vorschau">👁️</button>
|
|
${v.status === 'draft' ? `
|
|
<button class="btn btn-ghost btn-xs" onclick="editEmailVersion('${v.id}')" title="Bearbeiten">✏️</button>
|
|
<button class="btn btn-ghost btn-xs" onclick="submitEmailForReview('${v.id}')" title="Zur Prüfung">📤</button>
|
|
<button class="btn btn-ghost btn-xs" onclick="deleteEmailVersion('${v.id}')" title="Löschen">🗑️</button>
|
|
` : ''}
|
|
${v.status === 'review' ? `
|
|
<button class="btn btn-ghost btn-xs" onclick="showEmailApprovalDialogFor('${v.id}')" title="Genehmigen">✅</button>
|
|
<button class="btn btn-ghost btn-xs" onclick="rejectEmailVersion('${v.id}')" title="Ablehnen">❌</button>
|
|
` : ''}
|
|
${v.status === 'approved' ? `
|
|
<button class="btn btn-primary btn-xs" onclick="publishEmailVersion('${v.id}')" title="Veröffentlichen">🚀</button>
|
|
` : ''}
|
|
</td>
|
|
</tr>
|
|
`).join('')}
|
|
</tbody>
|
|
</table>
|
|
`;
|
|
}
|
|
|
|
function showEmailVersionForm() {
|
|
document.getElementById('email-version-form').style.display = 'block';
|
|
document.getElementById('email-version-form-title').textContent = 'Neue E-Mail-Version erstellen';
|
|
document.getElementById('email-version-id').value = '';
|
|
document.getElementById('email-version-number').value = '';
|
|
document.getElementById('email-version-subject').value = '';
|
|
document.getElementById('email-version-editor').innerHTML = '';
|
|
document.getElementById('email-version-text').value = '';
|
|
|
|
// Lade Default-Inhalt
|
|
const template = emailTemplates.find(t => t.id === currentEmailTemplateId);
|
|
if (template) {
|
|
loadDefaultEmailContent(template.type);
|
|
}
|
|
}
|
|
|
|
async function loadDefaultEmailContent(templateType) {
|
|
try {
|
|
const res = await fetch(`/api/consent/admin/email-templates/default/${templateType}`);
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
document.getElementById('email-version-subject').value = data.subject || '';
|
|
document.getElementById('email-version-editor').innerHTML = data.body_html || '';
|
|
document.getElementById('email-version-text').value = data.body_text || '';
|
|
}
|
|
} catch (e) {
|
|
console.error('Error loading default content:', e);
|
|
}
|
|
}
|
|
|
|
function hideEmailVersionForm() {
|
|
document.getElementById('email-version-form').style.display = 'none';
|
|
}
|
|
|
|
async function saveEmailVersion() {
|
|
const versionId = document.getElementById('email-version-id').value;
|
|
const templateId = currentEmailTemplateId;
|
|
const version = document.getElementById('email-version-number').value.trim();
|
|
const language = document.getElementById('email-version-lang').value;
|
|
const subject = document.getElementById('email-version-subject').value.trim();
|
|
const bodyHtml = document.getElementById('email-version-editor').innerHTML;
|
|
const bodyText = document.getElementById('email-version-text').value.trim();
|
|
|
|
if (!version || !subject || !bodyHtml) {
|
|
showToast('Bitte füllen Sie alle Pflichtfelder aus', 'error');
|
|
return;
|
|
}
|
|
|
|
const data = {
|
|
template_id: templateId,
|
|
version: version,
|
|
language: language,
|
|
subject: subject,
|
|
body_html: bodyHtml,
|
|
body_text: bodyText || stripHtml(bodyHtml)
|
|
};
|
|
|
|
try {
|
|
let res;
|
|
if (versionId) {
|
|
// Update existing version
|
|
res = await fetch(`/api/consent/admin/email-template-versions/${versionId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
} else {
|
|
// Create new version
|
|
res = await fetch('/api/consent/admin/email-template-versions', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
}
|
|
|
|
if (!res.ok) {
|
|
const error = await res.json();
|
|
throw new Error(error.detail || 'Fehler beim Speichern');
|
|
}
|
|
|
|
showToast('E-Mail-Version gespeichert!', 'success');
|
|
hideEmailVersionForm();
|
|
loadEmailTemplateVersions();
|
|
} catch (e) {
|
|
showToast('Fehler: ' + e.message, 'error');
|
|
}
|
|
}
|
|
|
|
function stripHtml(html) {
|
|
const div = document.createElement('div');
|
|
div.innerHTML = html;
|
|
return div.textContent || div.innerText || '';
|
|
}
|
|
|
|
async function editEmailVersion(versionId) {
|
|
try {
|
|
const res = await fetch(`/api/consent/admin/email-template-versions/${versionId}`);
|
|
if (!res.ok) throw new Error('Version nicht gefunden');
|
|
const version = await res.json();
|
|
|
|
document.getElementById('email-version-form').style.display = 'block';
|
|
document.getElementById('email-version-form-title').textContent = 'E-Mail-Version bearbeiten';
|
|
document.getElementById('email-version-id').value = versionId;
|
|
document.getElementById('email-version-number').value = version.version;
|
|
document.getElementById('email-version-lang').value = version.language;
|
|
document.getElementById('email-version-subject').value = version.subject;
|
|
document.getElementById('email-version-editor').innerHTML = version.body_html;
|
|
document.getElementById('email-version-text').value = version.body_text || '';
|
|
} catch (e) {
|
|
showToast('Fehler beim Laden der Version', 'error');
|
|
}
|
|
}
|
|
|
|
async function deleteEmailVersion(versionId) {
|
|
if (!confirm('Möchten Sie diese Version wirklich löschen?')) return;
|
|
|
|
try {
|
|
const res = await fetch(`/api/consent/admin/email-template-versions/${versionId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
if (!res.ok) throw new Error('Fehler beim Löschen');
|
|
showToast('Version gelöscht', 'success');
|
|
loadEmailTemplateVersions();
|
|
} catch (e) {
|
|
showToast('Fehler beim Löschen', 'error');
|
|
}
|
|
}
|
|
|
|
async function submitEmailForReview(versionId) {
|
|
try {
|
|
const res = await fetch(`/api/consent/admin/email-template-versions/${versionId}/submit`, {
|
|
method: 'POST'
|
|
});
|
|
if (!res.ok) throw new Error('Fehler');
|
|
showToast('Zur Prüfung eingereicht', 'success');
|
|
loadEmailTemplateVersions();
|
|
} catch (e) {
|
|
showToast('Fehler beim Einreichen', 'error');
|
|
}
|
|
}
|
|
|
|
function showEmailApprovalDialogFor(versionId) {
|
|
currentEmailVersionId = versionId;
|
|
document.getElementById('email-approval-dialog').style.display = 'flex';
|
|
document.getElementById('email-approval-comment').value = '';
|
|
}
|
|
|
|
function hideEmailApprovalDialog() {
|
|
document.getElementById('email-approval-dialog').style.display = 'none';
|
|
currentEmailVersionId = null;
|
|
}
|
|
|
|
async function submitEmailApproval() {
|
|
if (!currentEmailVersionId) return;
|
|
|
|
const comment = document.getElementById('email-approval-comment').value.trim();
|
|
|
|
try {
|
|
const res = await fetch(`/api/consent/admin/email-template-versions/${currentEmailVersionId}/approve`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ comment: comment })
|
|
});
|
|
if (!res.ok) throw new Error('Fehler');
|
|
showToast('Version genehmigt', 'success');
|
|
hideEmailApprovalDialog();
|
|
loadEmailTemplateVersions();
|
|
} catch (e) {
|
|
showToast('Fehler bei der Genehmigung', 'error');
|
|
}
|
|
}
|
|
|
|
async function rejectEmailVersion(versionId) {
|
|
const reason = prompt('Ablehnungsgrund:');
|
|
if (!reason) return;
|
|
|
|
try {
|
|
const res = await fetch(`/api/consent/admin/email-template-versions/${versionId}/reject`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ reason: reason })
|
|
});
|
|
if (!res.ok) throw new Error('Fehler');
|
|
showToast('Version abgelehnt', 'success');
|
|
loadEmailTemplateVersions();
|
|
} catch (e) {
|
|
showToast('Fehler bei der Ablehnung', 'error');
|
|
}
|
|
}
|
|
|
|
async function publishEmailVersion(versionId) {
|
|
if (!confirm('Möchten Sie diese Version veröffentlichen? Die vorherige Version wird archiviert.')) return;
|
|
|
|
try {
|
|
const res = await fetch(`/api/consent/admin/email-template-versions/${versionId}/publish`, {
|
|
method: 'POST'
|
|
});
|
|
if (!res.ok) throw new Error('Fehler');
|
|
showToast('Version veröffentlicht!', 'success');
|
|
loadEmailTemplateVersions();
|
|
} catch (e) {
|
|
showToast('Fehler beim Veröffentlichen', 'error');
|
|
}
|
|
}
|
|
|
|
async function previewEmailVersionById(versionId) {
|
|
try {
|
|
const res = await fetch(`/api/consent/admin/email-template-versions/${versionId}/preview`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({})
|
|
});
|
|
if (!res.ok) throw new Error('Fehler');
|
|
const data = await res.json();
|
|
|
|
document.getElementById('email-preview-subject').textContent = data.subject;
|
|
document.getElementById('email-preview-content').innerHTML = data.body_html;
|
|
document.getElementById('email-preview-dialog').style.display = 'flex';
|
|
currentEmailVersionId = versionId;
|
|
} catch (e) {
|
|
showToast('Fehler bei der Vorschau', 'error');
|
|
}
|
|
}
|
|
|
|
function previewEmailVersion() {
|
|
const subject = document.getElementById('email-version-subject').value;
|
|
const bodyHtml = document.getElementById('email-version-editor').innerHTML;
|
|
|
|
document.getElementById('email-preview-subject').textContent = subject;
|
|
document.getElementById('email-preview-content').innerHTML = bodyHtml;
|
|
document.getElementById('email-preview-dialog').style.display = 'flex';
|
|
}
|
|
|
|
function hideEmailPreview() {
|
|
document.getElementById('email-preview-dialog').style.display = 'none';
|
|
}
|
|
|
|
async function sendTestEmail() {
|
|
const email = document.getElementById('email-test-address').value.trim();
|
|
if (!email) {
|
|
showToast('Bitte geben Sie eine E-Mail-Adresse ein', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!currentEmailVersionId) {
|
|
showToast('Keine Version ausgewählt', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const res = await fetch(`/api/consent/admin/email-template-versions/${currentEmailVersionId}/send-test`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email: email })
|
|
});
|
|
if (!res.ok) throw new Error('Fehler');
|
|
showToast('Test-E-Mail gesendet!', 'success');
|
|
} catch (e) {
|
|
showToast('Fehler beim Senden der Test-E-Mail', 'error');
|
|
}
|
|
}
|
|
|
|
async function initializeEmailTemplates() {
|
|
if (!confirm('Möchten Sie alle Standard-E-Mail-Templates initialisieren?')) return;
|
|
|
|
try {
|
|
const res = await fetch('/api/consent/admin/email-templates/initialize', {
|
|
method: 'POST'
|
|
});
|
|
if (!res.ok) throw new Error('Fehler');
|
|
showToast('Templates initialisiert!', 'success');
|
|
loadEmailTemplates();
|
|
} catch (e) {
|
|
showToast('Fehler bei der Initialisierung', 'error');
|
|
}
|
|
}
|
|
|
|
// E-Mail Editor Helpers
|
|
function formatEmailDoc(command) {
|
|
document.execCommand(command, false, null);
|
|
document.getElementById('email-version-editor').focus();
|
|
}
|
|
|
|
function formatEmailBlock(tag) {
|
|
document.execCommand('formatBlock', false, '<' + tag + '>');
|
|
document.getElementById('email-version-editor').focus();
|
|
}
|
|
|
|
function insertEmailVariable() {
|
|
const variable = prompt('Variablenname eingeben (z.B. user_name, reset_link):');
|
|
if (variable) {
|
|
document.execCommand('insertText', false, '{{' + variable + '}}');
|
|
}
|
|
}
|
|
|
|
function insertEmailLink() {
|
|
const url = prompt('Link-URL:');
|
|
if (url) {
|
|
const text = prompt('Link-Text:', url);
|
|
document.execCommand('insertHTML', false, `<a href="${url}" style="color: #5B21B6;">${text}</a>`);
|
|
}
|
|
}
|
|
|
|
function insertEmailButton() {
|
|
const url = prompt('Button-Link:');
|
|
if (url) {
|
|
const text = prompt('Button-Text:', 'Klicken');
|
|
const buttonHtml = `<table cellpadding="0" cellspacing="0" style="margin: 16px 0;"><tr><td style="background: #5B21B6; border-radius: 6px; padding: 12px 24px;"><a href="${url}" style="color: white; text-decoration: none; font-weight: 600;">${text}</a></td></tr></table>`;
|
|
document.execCommand('insertHTML', false, buttonHtml);
|
|
}
|
|
}
|
|
"""
|