The page-load restore was one-shot and bailed when a completed scan's
replayed scan_phase left a running flag set; sse_replay_done (the other
retry) only fires for a non-empty replay buffer, which is empty after a
restart — so refreshing post-update showed a blank grid despite the
results being in the DB. The watchdog now retries the restore on each
4s poll while nothing is shown and no scan runs, clearing stale flags
first. /api/scan/status also reports google_running separately so a
refresh during a live Google scan is no longer treated as idle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Immutable audit_log table in the scanner DB records every significant
admin action (profile save/delete, token create/revoke, PIN changes,
source add/update/delete, scheduler job changes, scan start/stop, SMTP
save, dispositions, item delete/redact). GET /api/audit_log exposes
entries newest-first. New Audit Log tab in the Settings modal renders
the table on demand. Settings modal widened 540→640 px and tab labels
set to white-space:nowrap so the six-tab row fits on one line.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs in the abort mechanism:
1. POST /api/scan/stop only set state._scan_abort (M365/file abort event)
but never touched state._google_scan_abort. Now sets both.
2. _check_abort() inside _run_google_scan imported gdpr_scanner._scan_abort
(= state._scan_abort, the M365 event) instead of using the module-level
_scan_abort alias (= state._google_scan_abort). This meant the dedicated
/api/google/scan/cancel endpoint — which correctly sets _google_scan_abort
— was silently ignored by the scan loop. Fixed to use the module-level
alias consistently. Also aligned the end-of-scan checkpoint-clear check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>