fix UI 500 + slow backup with millions of processed_mails

- fix: Starlette 1.0 changed TemplateResponse signature — request is now
  the first positional argument, not nested inside the context dict.
  All HTML routes returned 500 (unhashable dict in jinja2 template cache)
  after the image rebuild picked up the new starlette version.
- fix: processed_mails (1.7M rows in production: 1 account × 12 rules ×
  141k inbox mails) made backup export hit 558 MB / 90s. Moved to opt-in
  checkbox in the UI alongside the logs option, default off.
- yield_per for streaming the processed_mails query when included.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 20:37:32 +02:00
parent 7e7ec67e58
commit b77b192b56
4 changed files with 50 additions and 27 deletions
+11 -2
View File
@@ -9,6 +9,10 @@
<article>
<header><h3>Backup erstellen</h3></header>
<p>Gesamte Konfiguration als JSON-Datei herunterladen.</p>
<label style="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.25rem;">
<input type="checkbox" id="backup-include-processed" style="margin-bottom:0;">
Verarbeitungsstatus mit sichern <small style="opacity:0.7;">(Marker, welche Mails schon verarbeitet wurden — kann sehr groß werden)</small>
</label>
<label style="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.5rem;">
<input type="checkbox" id="backup-include-logs" style="margin-bottom:0;">
Logs mit sichern <small style="opacity:0.7;">(Datei wird deutlich größer)</small>
@@ -54,13 +58,18 @@
<script>
async function exportBackup() {
const includeLogs = document.getElementById('backup-include-logs').checked;
const includeProcessed = document.getElementById('backup-include-processed').checked;
const btn = document.getElementById('backup-download-btn');
const statusDiv = document.getElementById('backup-export-status');
btn.setAttribute('aria-busy', 'true');
btn.disabled = true;
statusDiv.innerHTML = `<small>Backup wird erstellt${includeLogs ? ' (Logs werden geladen, kann dauern...)' : ''}</small>`;
const heavy = includeLogs || includeProcessed;
statusDiv.innerHTML = `<small>Backup wird erstellt${heavy ? ' (kann dauern...)' : ''}</small>`;
try {
const url = `/api/yaml/backup${includeLogs ? '?include_logs=true' : ''}`;
const params = new URLSearchParams();
if (includeLogs) params.set('include_logs', 'true');
if (includeProcessed) params.set('include_processed', 'true');
const url = `/api/yaml/backup${params.toString() ? '?' + params.toString() : ''}`;
const resp = await fetch(url);
if (!resp.ok) {
const text = await resp.text().catch(() => '');