add "reset all processed" button + endpoint

POST /api/filters/reset-all-processed drops and recreates the
processed_mails table — sekundenschnell auch bei Millionen Zeilen
via DROP+CREATE (same pattern as the fast clear-logs). Safe because
already-moved mails are no longer in the source folder, so a fresh
re-evaluation cannot re-move them, only pick up ones that were
skipped due to a rule bug (e.g. the has_attachment fix).

Button "Alle Regeln zurücksetzen" next to "Neue Regel" on the filter
list, with a confirmation dialog and result feedback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 12:39:03 +02:00
co-authored by Claude Opus 4.7
parent 51ed9e4235
commit 1c30bffb5e
2 changed files with 42 additions and 2 deletions
+23 -2
View File
@@ -1,10 +1,10 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func
from sqlalchemy import func, text
from sqlalchemy.orm import Session
import logging
from app.database import get_db
from app.database import engine, get_db
from app.models.db_models import (
Account,
FilterAction,
@@ -33,6 +33,27 @@ def _reset_processed_for_rule(db: Session, rule_id: int) -> int:
router = APIRouter(prefix="/api/filters", tags=["filters"])
@router.post("/reset-all-processed")
def reset_all_processed(db: Session = Depends(get_db)):
"""Setzt den 'verarbeitet'-Status ALLER Mails zurück — alle Regeln bewerten
beim nächsten Poll wieder jede Mail im Ordner neu. Sicher, weil schon
verschobene Mails nicht mehr in der Quelle liegen und daher nicht erneut
verschoben werden können. Nutzt DROP+CREATE statt DELETE, damit auch
bei Millionen Zeilen sekundenschnell."""
try:
row_count = db.query(ProcessedMail).count()
db.close() # Sitzung freigeben, damit DDL nicht am Lock hängt
with engine.begin() as conn:
ProcessedMail.__table__.drop(conn, checkfirst=True)
ProcessedMail.__table__.create(conn, checkfirst=True)
conn.execute(text("PRAGMA wal_checkpoint(TRUNCATE)"))
logger.info("ProcessedMail vollständig zurückgesetzt (%d Einträge entfernt)", row_count)
return {"reset": row_count}
except Exception as e:
logger.exception("reset-all-processed fehlgeschlagen")
raise HTTPException(500, f"Fehler beim Zurücksetzen: {e}")
@router.post("/retry-failed")
def retry_failed_fetches(account_id: int | None = None, db: Session = Depends(get_db)):
"""Löscht ProcessedMail-Einträge für UIDs, für die es einen ERROR-Log
+19
View File
@@ -224,6 +224,8 @@ function renderFilters() {
let html = `
<div style="display:flex; gap:0.75rem; align-items:center; margin-bottom:1rem; flex-wrap:wrap;">
<button onclick="openNewFilter()" style="margin-bottom:0;">Neue Regel</button>
<button class="outline" onclick="resetAllProcessed()" title="Verwirft den 'verarbeitet'-Marker aller Mails. Beim nächsten Poll werden alle Ordner-Mails neu gegen alle Regeln geprüft. Sicher — schon verschobene Mails sind nicht mehr in der Quelle."
style="margin-bottom:0;">Alle Regeln zurücksetzen</button>
<input type="search" id="filter-search" placeholder="Filter durchsuchen..."
value="${searchTerm.replace(/"/g, '&quot;')}"
oninput="renderFilters()"
@@ -530,6 +532,23 @@ async function deleteFilter(id) {
loadFilters();
}
async function resetAllProcessed() {
if (!confirm('Alle Mails werden beim nächsten Poll erneut von allen Regeln geprüft. Fortfahren?')) return;
try {
const resp = await fetch('/api/filters/reset-all-processed', {method: 'POST'});
const text = await resp.text();
let data = null;
try { data = JSON.parse(text); } catch {}
if (!resp.ok) {
alert('Fehler: ' + (data?.detail || text || `HTTP ${resp.status}`));
return;
}
alert(`${data?.reset ?? 0} Einträge zurückgesetzt. Beim nächsten Poll (max ~1-2 min) wird alles neu geprüft.`);
} catch(e) {
alert('Netzwerk-Fehler: ' + e.message);
}
}
// --- Ordner-Browser (Baumansicht) ---
let folderTargetInputName = null;