261 lines
11 KiB
HTML
261 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Emails{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<h1>Email-Verwaltung</h1>
|
|
<p>Posteingang und Email-Versand</p>
|
|
</div>
|
|
|
|
{% if not email_config_valid %}
|
|
<div class="alert alert-warning">
|
|
<strong>Konfiguration erforderlich</strong><br>
|
|
<small>Setze <code>IMAP_SERVER</code>, <code>SMTP_SERVER</code>, <code>EMAIL_ADDRESS</code>, <code>EMAIL_PASSWORD</code> in der <code>.env</code>-Datei.</small>
|
|
</div>
|
|
{% else %}
|
|
<div class="alert alert-success">
|
|
<strong>Verbunden</strong> · {{ current_email }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="row g-4">
|
|
<!-- Neue Email -->
|
|
<div class="col-lg-4">
|
|
<div class="card">
|
|
<div class="card-header bg-dark">
|
|
<h5 class="mb-0">Neue Email</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST" action="/emails">
|
|
<input type="hidden" name="action" value="send">
|
|
<div class="mb-3">
|
|
<label for="to_address" class="form-label">An</label>
|
|
<input type="email" class="form-control" id="to_address" name="to_address" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="subject" class="form-label">Betreff</label>
|
|
<input type="text" class="form-control" id="subject" name="subject" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="body" class="form-label">Nachricht</label>
|
|
<textarea class="form-control" id="body" name="body" rows="8" required></textarea>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100">Versenden</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Posteingang -->
|
|
<div class="col-lg-8">
|
|
<div class="card">
|
|
<div class="card-header bg-dark d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">Posteingang</h5>
|
|
<span class="badge bg-secondary">{{ emails|length }} Emails</span>
|
|
</div>
|
|
<div class="card-body p-0" style="max-height:600px;overflow-y:auto;">
|
|
{% if email_config_valid and emails %}
|
|
{% if emails[0].error is defined and emails[0].error %}
|
|
<div class="alert alert-danger m-3">{{ emails[0].error }}</div>
|
|
{% else %}
|
|
<ul class="list-group list-group-flush">
|
|
{% for mail in emails %}
|
|
<li class="list-group-item list-group-item-action d-flex align-items-start gap-2" style="cursor:pointer;">
|
|
<div class="flex-grow-1" onclick="viewEmail('{{ mail.id }}', '{{ mail.subject|e }}', '{{ mail.from|e }}')">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<strong style="font-size:.875rem;">{{ mail.subject }}</strong>
|
|
<small style="color:var(--text-muted);white-space:nowrap;margin-left:.5rem;">{{ mail.date[:10] }}</small>
|
|
</div>
|
|
<div style="font-size:.8rem;color:var(--text-muted);">{{ mail.from }}</div>
|
|
<div style="font-size:.78rem;color:var(--text-muted);margin-top:.2rem;">{{ mail.preview }}</div>
|
|
</div>
|
|
<form method="POST" action="/emails/inbox/{{ mail.id }}/delete"
|
|
onsubmit="return confirm('Email aus Posteingang löschen?')" style="flex-shrink:0;">
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" title="Löschen">✕</button>
|
|
</form>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% endif %}
|
|
{% elif not email_config_valid %}
|
|
<div class="text-center py-5" style="color:var(--text-muted);">
|
|
<p>Email-Konfiguration erforderlich</p>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5" style="color:var(--text-muted);">
|
|
<p>Keine Emails vorhanden</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Email Modal -->
|
|
<div class="modal fade" id="emailModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="emailSubject"></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p style="color:var(--text-muted);font-size:.85rem;"><strong>Von:</strong> <span id="emailFrom"></span></p>
|
|
<hr>
|
|
<pre id="emailBody" style="background:var(--bg-elevated);color:var(--text-primary);
|
|
border-radius:var(--radius-sm);padding:1rem;white-space:pre-wrap;
|
|
max-height:400px;overflow-y:auto;font-size:.85rem;">Wird geladen…</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gesendete Emails Log -->
|
|
<div class="row g-4 mt-2">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">Gesendete Emails</h5>
|
|
<span class="badge bg-secondary">{{ sent_emails|length }} Einträge</span>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
{% if sent_emails %}
|
|
<div class="table-responsive" style="max-height:600px;overflow-y:auto;">
|
|
<table class="table table-hover mb-0" style="font-size:.83rem;">
|
|
<thead style="position:sticky;top:0;z-index:1;background:var(--bg-elevated);">
|
|
<tr>
|
|
<th style="width:40px;">#</th>
|
|
<th style="width:170px;">Zeitpunkt</th>
|
|
<th>An</th>
|
|
<th>Betreff</th>
|
|
<th style="width:160px;">Von Agent</th>
|
|
<th style="width:70px;">Task</th>
|
|
<th style="width:60px;">Status</th>
|
|
<th style="width:80px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for mail in sent_emails %}
|
|
<tr id="sent-row-{{ mail.id }}">
|
|
<td style="color:var(--text-muted);">{{ mail.id }}</td>
|
|
<td style="color:var(--text-muted);white-space:nowrap;">{{ mail.sent_at[:16].replace('T',' ') }}</td>
|
|
<td style="font-weight:500;">{{ mail.to_address }}</td>
|
|
<td>{{ mail.subject }}</td>
|
|
<td style="color:var(--text-muted);">{{ mail.triggered_by }}</td>
|
|
<td style="color:var(--text-muted);">{% if mail.task_id %}#{{ mail.task_id }}{% endif %}</td>
|
|
<td>
|
|
{% if mail.status == 'sent' %}
|
|
<span class="badge bg-success">gesendet</span>
|
|
{% elif mail.status == 'error' %}
|
|
<span class="badge bg-danger">Fehler</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">{{ mail.status }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="d-flex gap-1">
|
|
<button class="btn btn-sm btn-secondary sent-read-btn"
|
|
data-id="{{ mail.id }}"
|
|
data-to="{{ mail.to_address }}"
|
|
data-subject="{{ mail.subject|e }}"
|
|
data-date="{{ mail.sent_at }}"
|
|
data-agent="{{ mail.triggered_by }}"
|
|
data-body="{{ (mail.body or '')|e }}"
|
|
title="Lesen">👁</button>
|
|
<button class="btn btn-sm btn-danger sent-delete-btn"
|
|
data-id="{{ mail.id }}"
|
|
title="Löschen">✕</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5" style="color:var(--text-muted);">Noch keine Emails gesendet.</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sent Email Detail Modal -->
|
|
<div class="modal fade" id="sentEmailModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<div>
|
|
<h5 class="modal-title mb-1" id="sentEmailSubject"></h5>
|
|
<div style="font-size:.8rem;color:var(--text-muted);">
|
|
An: <span id="sentEmailTo"></span> ·
|
|
<span id="sentEmailDate"></span> ·
|
|
Von: <span id="sentEmailAgent"></span>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body p-0">
|
|
<pre id="sentEmailBody" style="background:var(--bg-elevated);color:var(--text-primary);
|
|
border-radius:0;padding:1.25rem;white-space:pre-wrap;margin:0;
|
|
font-size:.84rem;line-height:1.6;max-height:70vh;overflow-y:auto;"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function viewEmail(emailId, subject, from) {
|
|
document.getElementById('emailSubject').textContent = subject;
|
|
document.getElementById('emailFrom').textContent = from;
|
|
document.getElementById('emailBody').textContent = 'Wird geladen…';
|
|
const modal = new bootstrap.Modal(document.getElementById('emailModal'));
|
|
modal.show();
|
|
fetch('/emails/' + emailId)
|
|
.then(r => r.json())
|
|
.then(d => { document.getElementById('emailBody').textContent = d.content; })
|
|
.catch(e => { document.getElementById('emailBody').textContent = 'Fehler: ' + e.message; });
|
|
}
|
|
|
|
// Lesen
|
|
document.addEventListener('click', function(e) {
|
|
const btn = e.target.closest('.sent-read-btn');
|
|
if (!btn) return;
|
|
document.getElementById('sentEmailSubject').textContent = btn.dataset.subject;
|
|
document.getElementById('sentEmailTo').textContent = btn.dataset.to;
|
|
document.getElementById('sentEmailDate').textContent = btn.dataset.date.replace('T',' ').slice(0,16);
|
|
document.getElementById('sentEmailAgent').textContent = btn.dataset.agent;
|
|
document.getElementById('sentEmailBody').textContent = btn.dataset.body || '(kein Inhalt gespeichert)';
|
|
new bootstrap.Modal(document.getElementById('sentEmailModal')).show();
|
|
});
|
|
|
|
// Löschen
|
|
document.addEventListener('click', function(e) {
|
|
const btn = e.target.closest('.sent-delete-btn');
|
|
if (!btn) return;
|
|
const id = btn.dataset.id;
|
|
if (!confirm('Eintrag #' + id + ' aus dem Log löschen?')) return;
|
|
fetch('/api/sent-emails/' + id + '/delete', {method: 'POST', credentials: 'same-origin'})
|
|
.then(r => {
|
|
if (r.redirected || r.status === 302 || r.status === 401) {
|
|
window.location.reload(); return null;
|
|
}
|
|
const ct = r.headers.get('content-type') || '';
|
|
if (!ct.includes('application/json')) throw new Error('Unerwartete Antwort (HTTP ' + r.status + ')');
|
|
return r.json();
|
|
})
|
|
.then(d => {
|
|
if (!d) return;
|
|
if (d.success) {
|
|
const row = document.getElementById('sent-row-' + id);
|
|
if (row) row.remove();
|
|
} else {
|
|
alert('Fehler: ' + d.message);
|
|
}
|
|
})
|
|
.catch(err => alert('Fehler: ' + err.message));
|
|
});
|
|
</script>
|
|
{% endblock %}
|