Make Stop stop: no replay, no restart, immediate feedback
Build — Windows, Linux & macOS / GDPRScanner / linux (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / macos (push) Has been cancelled
Build — Windows, Linux & macOS / GDPRScanner / windows (push) Has been cancelled
Build — Windows, Linux & macOS / Create GitHub Release (push) Has been cancelled

Pressing Stop could look like the scan restarting, and for scheduled jobs
it really did restart the Google stage.

- Scheduler: check the abort flag between stages and never clear the
  Google abort after a Stop (that started a fresh all-users Google scan).
  A stopped job is recorded as "cancelled" and skips email/retention;
  scheduler_done carries cancelled=true.
- SSE: scan_cancelled and file_scan_done now end the current scan like
  scan_done, and sse.replay_messages() never replays a finished scan.
  Previously a stopped M365 scan (and every file scan) stayed "current",
  so each reconnect replayed it and re-showed Stop + progress.
- Frontend: scan_cancelled clears the M365 running flag and only tears
  down the UI when no Google/file scan is still running; scheduler_done /
  scheduler_error clear all running flags.
- run_scan: Stop during collection now ends the scan (checkpoint, finish,
  scan_cancelled) instead of announcing scan_start / "Scanning…".
- Stop button: switches to a disabled "Stopping…" state on click; all
  show/hide goes through _setStopBtnDisplay(), which resets it when hidden
  or when a new scan starts.

Adds tests/test_stop_scan.py (10 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #2.
This commit is contained in:
StyxX65andClaude Opus 5.5 committed 2026-09-28 10:03:12 +02:00
1 parent 9cf23da907
commit 606d8e944b
12 files changed
+372 -64

No files matched your search

+56 -21
View File
@@ -317,7 +317,7 @@ function _attachScanListeners(source) {
else if (phaseSrc === 'file') { S._fileScanRunning = true; }
else { S._m365ScanRunning = true; }
document.getElementById('scanBtn').disabled = true;
document.getElementById('stopBtn').style.display = 'inline-block';
_setStopBtnDisplay('inline-block');
_renderProgressSegments();
}
_setProgressPhase(d.phase);
@@ -330,9 +330,9 @@ function _attachScanListeners(source) {
: (d.total > 0 ? Math.round((d.index || d.completed || 0) / d.total * 100) : 0);
S._srcPct[src] = pct;
// If reconnecting mid-scan the running flag may not be set yet — ensure segment exists
if (src === 'm365' && !S._m365ScanRunning) { S._m365ScanRunning = true; document.getElementById('scanBtn').disabled = true; document.getElementById('stopBtn').style.display = 'inline-block'; _renderProgressSegments(); }
if (src === 'google' && !S._googleScanRunning) { S._googleScanRunning = true; document.getElementById('scanBtn').disabled = true; document.getElementById('stopBtn').style.display = 'inline-block'; _renderProgressSegments(); }
if (src === 'file' && !S._fileScanRunning) { S._fileScanRunning = true; document.getElementById('scanBtn').disabled = true; document.getElementById('stopBtn').style.display = 'inline-block'; _renderProgressSegments(); }
if (src === 'm365' && !S._m365ScanRunning) { S._m365ScanRunning = true; document.getElementById('scanBtn').disabled = true; _setStopBtnDisplay('inline-block'); _renderProgressSegments(); }
if (src === 'google' && !S._googleScanRunning) { S._googleScanRunning = true; document.getElementById('scanBtn').disabled = true; _setStopBtnDisplay('inline-block'); _renderProgressSegments(); }
if (src === 'file' && !S._fileScanRunning) { S._fileScanRunning = true; document.getElementById('scanBtn').disabled = true; _setStopBtnDisplay('inline-block'); _renderProgressSegments(); }
var fill = document.getElementById('progressFill_' + src);
if (fill) fill.style.width = pct + '%';
document.getElementById('progressFile').textContent = d.file || '';
@@ -368,15 +368,20 @@ function _attachScanListeners(source) {
log((d.file ? d.file + ': ' : '') + d.error, 'err');
});
source.addEventListener('scan_cancelled', function() {
if (S._userStartedScan) {
// scan_cancelled is the M365 engine's "stopped" event. Clear its running
// flag (leaving it set made later replays/status polls treat the stopped
// scan as live) and, like scan_done, only tear down the UI once no Google
// or file scan is still running.
S._m365ScanRunning = false;
var _anyRunning = S._googleScanRunning || S._fileScanRunning;
if (S._userStartedScan && !_anyRunning) {
S._userStartedScan = false;
if (S.es) { S.es.close(); S.es = null; }
}
document.getElementById('scanBtn').disabled = false;
document.getElementById('stopBtn').style.display = 'none';
_clearProgressBar();
setLogLive('');
log('Scan stopped.', 'warn');
document.getElementById('scanBtn').disabled = _anyRunning;
_setStopBtnDisplay(_anyRunning ? 'inline-block' : 'none');
if (_anyRunning) { _renderProgressSegments(); } else { _clearProgressBar(); setLogLive(''); }
log(t('m365_scan_stopped', 'Scan stopped.'), 'warn');
});
source.addEventListener('scan_done', function(e) {
var d = JSON.parse(e.data);
@@ -401,7 +406,7 @@ function _attachScanListeners(source) {
}
if (!_anyRunning) setLogLive('');
document.getElementById('scanBtn').disabled = _anyRunning;
document.getElementById('stopBtn').style.display = _anyRunning ? 'inline-block' : 'none';
_setStopBtnDisplay(_anyRunning ? 'inline-block' : 'none');
if (!_anyRunning) _clearProgressBar();
document.getElementById('statsSection').style.display = 'block';
document.getElementById('statScanned').textContent = d.total_scanned;
@@ -437,7 +442,7 @@ function _attachScanListeners(source) {
}
setLogLive('');
document.getElementById('scanBtn').disabled = false;
document.getElementById('stopBtn').style.display = 'none';
_setStopBtnDisplay('none');
_clearProgressBar();
document.getElementById('statsSection').style.display = 'block';
document.getElementById('statsPill').style.display = 'block';
@@ -466,7 +471,7 @@ function _attachScanListeners(source) {
}
setLogLive('');
document.getElementById('scanBtn').disabled = false;
document.getElementById('stopBtn').style.display = 'none';
_setStopBtnDisplay('none');
_clearProgressBar();
document.getElementById('statsSection').style.display = 'block';
document.getElementById('statsPill').style.display = 'block';
@@ -502,7 +507,7 @@ function _attachSchedulerListeners(source) {
log('\uD83D\uDD50 ' + t('m365_sched_title','Scheduled scan') + ': ' + (d.job_name||'') + '\u2026');
// Show progress UI so scan_phase / scan_progress events are visible
document.getElementById('scanBtn').disabled = true;
document.getElementById('stopBtn').style.display = 'inline-block';
_setStopBtnDisplay('inline-block', true);
S._srcPct = { m365: 0, google: 0, file: 0 }; S._m365ScanRunning = true; _renderProgressSegments();
_setProgressPhase((d.job_name||'') + '\u2026');
document.getElementById('progressFile').textContent = '';
@@ -512,25 +517,32 @@ function _attachSchedulerListeners(source) {
// scheduler_started was missed (e.g. browser reconnected mid-scan)
console.log('[SSE] scan_start received');
document.getElementById('scanBtn').disabled = true;
document.getElementById('stopBtn').style.display = 'inline-block';
_setStopBtnDisplay('inline-block');
// Ensure at least the M365 segment is rendered (scan_start is M365-only)
if (!S._m365ScanRunning) { S._m365ScanRunning = true; _renderProgressSegments(); }
});
source.addEventListener('scheduler_done', function(e) {
var d = JSON.parse(e.data);
console.log('[SSE] scheduler_done received:', d);
// The job's stages are all over — scheduler_started set the M365 flag
S._m365ScanRunning = S._googleScanRunning = S._fileScanRunning = false;
document.getElementById('scanBtn').disabled = false;
document.getElementById('stopBtn').style.display = 'none';
_setStopBtnDisplay('none');
_clearProgressBar();
log('\u2713 ' + t('m365_sched_title','Scheduled scan') + ' ' + (d.job_name||'') + ' \u2014 ' + (d.flagged||0) + ' flagged', 'ok');
if (d.cancelled) {
log('\u23f9 ' + t('m365_sched_title','Scheduled scan') + ' ' + (d.job_name||'') + ' \u2014 ' + t('m365_sched_stopped','stopped') + ' (' + (d.flagged||0) + ' flagged)', 'warn');
} else {
log('\u2713 ' + t('m365_sched_title','Scheduled scan') + ' ' + (d.job_name||'') + ' \u2014 ' + (d.flagged||0) + ' flagged', 'ok');
}
markOverdueCards();
loadTrend();
});
source.addEventListener('scheduler_error', function(e) {
var d = JSON.parse(e.data);
console.log('[SSE] scheduler_error received:', d);
S._m365ScanRunning = S._googleScanRunning = S._fileScanRunning = false;
document.getElementById('scanBtn').disabled = false;
document.getElementById('stopBtn').style.display = 'none';
_setStopBtnDisplay('none');
_clearProgressBar();
log('\u26A0 ' + t('m365_sched_title','Scheduled scan') + ' failed: ' + (d.error||''), 'err');
});
@@ -564,7 +576,7 @@ function startScan(resume) {
S._progressCurrentUser = '';
_renderProgressSegments();
document.getElementById('scanBtn').disabled = true;
document.getElementById('stopBtn').style.display = 'inline-block';
_setStopBtnDisplay('inline-block', true);
// progress segments rendered by _renderProgressSegments() called above
document.getElementById('progressFile').textContent = '';
_setProgressPhase(t('scan_preparing', 'Preparing…'));
@@ -594,7 +606,7 @@ function startScan(resume) {
if (label === 'google') S._googleScanRunning = false;
if (!S._m365ScanRunning && !S._googleScanRunning && !S._fileScanRunning) {
document.getElementById('scanBtn').disabled = false;
document.getElementById('stopBtn').style.display = 'none';
_setStopBtnDisplay('none');
if (S.es) { S.es.close(); S.es = null; }
S._userStartedScan = false;
}
@@ -664,8 +676,30 @@ function startScan(resume) {
}
// Show/hide the Stop button. Hiding — or passing fresh=true when a new scan
// starts — resets it from its "Stopping…" state, so a new scan always gets a
// normal, clickable Stop button even if a previous scan never reported its end.
function _setStopBtnDisplay(display, fresh) {
const btn = document.getElementById('stopBtn');
if (!btn) return;
btn.style.display = display;
if (display === 'none' || fresh) {
btn.disabled = false;
btn.textContent = t('m365_btn_stop', 'Stop');
}
}
function stopScan() {
fetch('/api/scan/stop', {method:'POST'});
// Immediate feedback: the scan only notices the stop between items, which
// can take a while (e.g. OCR on a large PDF). Stays "Stopping…" until the
// scan's done/stopped event hides the button.
const btn = document.getElementById('stopBtn');
if (btn) { btn.disabled = true; btn.textContent = t('m365_btn_stopping', 'Stopping…'); }
log(t('m365_scan_stopping', 'Stopping scan — finishing the current item…'), 'warn');
fetch('/api/scan/stop', {method:'POST'}).catch(function(e) {
if (btn) { btn.disabled = false; btn.textContent = t('m365_btn_stop', 'Stop'); }
log('Stop failed: ' + e, 'err');
});
}
// ── Trend sparkline (#7) ──────────────────────────────────────────────────────
@@ -795,6 +829,7 @@ window._attachScanListeners = _attachScanListeners;
window._attachSchedulerListeners = _attachSchedulerListeners;
window.startScan = startScan;
window.stopScan = stopScan;
window._setStopBtnDisplay = _setStopBtnDisplay;
window.drawSparkline = drawSparkline;
window.loadTrend = loadTrend;
window.updateStats = updateStats;