- Add sent_emails table to DB for persistent outbox logging - send_email() now writes every outgoing mail (incl. errors) to sent_emails - parse_agent_commands() passes agent_key/task_id as triggered_by metadata - Fix @UPDATE_TEAM_MEMBER parser: now matches Identifier/TelegramID/Role/etc. format from system prompt (was expecting Email/Field/Value — never matched) - update_team_member() called correctly via **kwargs (was positional args bug) - Set Piotr telegram_id=1578034974 directly in DB - email_log.html: two-tab UI (Inbox Journal + Outbox), click-to-expand body - emails.html: per-message delete button in inbox list - New routes: DELETE inbox (IMAP expunge), journal entry, sent entry
180 lines
6.6 KiB
HTML
180 lines
6.6 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Email-Log{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header d-flex align-items-center justify-content-between">
|
|
<div>
|
|
<h1>Email-Log</h1>
|
|
<p>Posteingang-Journal und gesendete Emails</p>
|
|
</div>
|
|
<button class="btn btn-secondary btn-sm" onclick="location.reload()">Aktualisieren</button>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<ul class="nav nav-tabs mb-3" id="emailLogTabs">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" data-bs-toggle="tab" href="#tab-inbox">
|
|
Posteingang / Journal
|
|
<span class="badge bg-secondary ms-1">{{ journal_rows|length }}</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-bs-toggle="tab" href="#tab-sent">
|
|
Gesendet
|
|
<span class="badge bg-primary ms-1">{{ sent_rows|length }}</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content">
|
|
|
|
<!-- ── TAB: Posteingang / Journal ── -->
|
|
<div class="tab-pane fade show active" id="tab-inbox">
|
|
{% if not journal_rows %}
|
|
<div class="card">
|
|
<div class="card-body text-center py-5" style="color:var(--text-muted);">
|
|
<p style="font-size:2rem;">📭</p>
|
|
<p>Keine Journal-Einträge vorhanden.</p>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="card">
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover log-table mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Empfangen</th>
|
|
<th>Von</th>
|
|
<th>Betreff</th>
|
|
<th>Agent</th>
|
|
<th>Status</th>
|
|
<th style="width:60px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in journal_rows %}
|
|
<tr>
|
|
<td style="white-space:nowrap;">
|
|
<small style="color:var(--text-muted);">{{ row.received_at[:16] if row.received_at else '—' }}</small>
|
|
</td>
|
|
<td><small>{{ row.sender or '—' }}</small></td>
|
|
<td>{{ row.subject or '—' }}</td>
|
|
<td>
|
|
{% if row.agent_key %}
|
|
<span class="badge bg-primary badge-agent">{{ row.agent_key }}</span>
|
|
{% else %}
|
|
<span style="color:var(--text-muted);">—</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if row.status == 'completed' %}
|
|
<span class="status-replied">✓ completed</span>
|
|
{% elif row.status == 'skipped' %}
|
|
<span class="status-skipped">— skipped</span>
|
|
{% elif row.status == 'error' %}
|
|
<span class="status-error">✗ error</span>
|
|
{% elif row.status == 'queued' %}
|
|
<span style="color:var(--warning);">⏳ queued</span>
|
|
{% else %}
|
|
<span style="color:var(--text-muted);">{{ row.status }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<form method="POST" action="/emails/journal/{{ row.message_id | urlencode }}/delete"
|
|
onsubmit="return confirm('Journal-Eintrag löschen?')">
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" title="Löschen">✕</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- ── TAB: Gesendet ── -->
|
|
<div class="tab-pane fade" id="tab-sent">
|
|
{% if not sent_rows %}
|
|
<div class="card">
|
|
<div class="card-body text-center py-5" style="color:var(--text-muted);">
|
|
<p style="font-size:2rem;">📤</p>
|
|
<p>Noch keine gesendeten Emails im Log.<br>
|
|
<small>Ab jetzt werden alle ausgehenden Emails hier gespeichert.</small>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="card">
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover log-table mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Gesendet</th>
|
|
<th>An</th>
|
|
<th>Betreff</th>
|
|
<th>Ausgelöst von</th>
|
|
<th>Status</th>
|
|
<th style="width:60px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in sent_rows %}
|
|
<tr style="cursor:pointer;" onclick="toggleBody({{ row.id }})">
|
|
<td style="white-space:nowrap;">
|
|
<small style="color:var(--text-muted);">{{ row.sent_at[:16] if row.sent_at else '—' }}</small>
|
|
</td>
|
|
<td><small>{{ row.to_address or '—' }}</small></td>
|
|
<td>{{ row.subject or '—' }}</td>
|
|
<td>
|
|
<small style="color:var(--text-muted);">
|
|
{{ row.triggered_by or 'manual' }}
|
|
{% if row.task_id %}<span class="badge bg-secondary ms-1">Task #{{ row.task_id }}</span>{% endif %}
|
|
</small>
|
|
</td>
|
|
<td>
|
|
{% if row.status == 'sent' %}
|
|
<span class="status-replied">✓ gesendet</span>
|
|
{% elif row.status == 'error' %}
|
|
<span class="status-error">✗ Fehler</span>
|
|
{% else %}
|
|
<span style="color:var(--text-muted);">{{ row.status }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td onclick="event.stopPropagation()">
|
|
<form method="POST" action="/emails/sent/{{ row.id }}/delete"
|
|
onsubmit="return confirm('Eintrag löschen?')">
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" title="Löschen">✕</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<!-- Expandable body row -->
|
|
<tr id="body-{{ row.id }}" style="display:none;">
|
|
<td colspan="6" style="background:var(--bg-elevated);padding:1rem;">
|
|
<pre style="white-space:pre-wrap;font-size:.82rem;margin:0;color:var(--text-primary);">{{ row.body or '(kein Inhalt)' }}</pre>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
</div><!-- /.tab-content -->
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function toggleBody(id) {
|
|
const row = document.getElementById('body-' + id);
|
|
if (row) row.style.display = row.style.display === 'none' ? '' : 'none';
|
|
}
|
|
</script>
|
|
{% endblock %}
|