Skip to content

Commit 0cf301e

Browse files
committed
feat: add bulk delete functionality for completed scans with zero findings
1 parent ad7b2ce commit 0cf301e

4 files changed

Lines changed: 37 additions & 1 deletion

File tree

internal/api/ui/app.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ async function clearAllScans() {
126126
return callPageMethod('ScanActionsPage', 'clearAllScans');
127127
}
128128

129+
async function deleteScansNoFindings() {
130+
return callPageMethod('ScanActionsPage', 'deleteScansNoFindings');
131+
}
132+
129133
async function deleteDomainRecord(domain) {
130134
return callPageMethod('DomainActionsPage', 'deleteDomainRecord', [domain]);
131135
}

internal/api/ui/pages/app-exports.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ window.openScanResultsPage = openScanResultsPage;
6161
window.wireShellOnce = wireShellOnce;
6262
window.updateClock = updateClock;
6363
window.browseR2ForScan = browseR2ForScan;
64+
window.deleteScansNoFindings = deleteScansNoFindings;
6465
window.loadDomainSubdomains = loadDomainSubdomains;
6566
window.deleteDomainRecord = deleteDomainRecord;
6667
window.prevFilesPage = prevFilesPage;

internal/api/ui/pages/scan-actions.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,36 @@
7878
}
7979
}
8080

81+
async function deleteScansNoFindings() {
82+
const { recent_scans = [] } = window.state.scans || {};
83+
const ids = recent_scans.filter(s => {
84+
const filesUploaded = s.files_uploaded || s.FilesUploaded || 0;
85+
const statusRaw = (s.status || s.Status || '').toLowerCase();
86+
const done = ['completed', 'done', 'failed', 'stopped', 'cancelled'].includes(statusRaw);
87+
return done && filesUploaded === 0;
88+
}).map(s => s.scan_id || s.ScanID).filter(Boolean);
89+
90+
if (!ids.length) {
91+
window.showToast('info', 'No empty scans', 'No completed scans with 0 findings were found.');
92+
return;
93+
}
94+
95+
if (!confirm(`Found ${ids.length} completed/stopped scan(s) with 0 findings. Delete them and their artifacts?`)) return;
96+
97+
try {
98+
const res = await window.apiPost('/api/scans/bulk-delete', { scan_ids: ids });
99+
let msg = `Removed ${res.deleted} scan(s).`;
100+
if (res.skipped_active) msg += ` ${res.skipped_active} skipped (still active).`;
101+
if (res.failed) msg += ` ${res.failed} failed.`;
102+
window.showToast(res.ok && !res.failed ? 'success' : 'error', res.ok ? 'Bulk delete done' : 'Some deletes failed', msg);
103+
window.loadStats();
104+
window.loadScans();
105+
} catch (e) {
106+
window.showToast('error', 'Bulk delete failed', e.message);
107+
}
108+
}
109+
110+
81111
async function pauseScan(scanID) {
82112
try {
83113
await window.apiPost(`/api/scans/${encodeURIComponent(scanID)}/pause`, {});
@@ -107,6 +137,7 @@
107137
toggleSelectAllRecentScans,
108138
deleteSelectedScans,
109139
clearAllScans,
140+
deleteScansNoFindings,
110141
pauseScan,
111142
resumeScan,
112143
};

internal/api/ui/pages/scans-page.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@
326326
if (filteredActive.length) {
327327
html += `<div class="card" style="margin-bottom:20px"><div class="card-header"><div class="card-title">⚡ Active Scans <span class="badge badge-running">${filteredActive.length}</span></div></div><div class="card-body">${filteredActive.map((s) => scanItemHtml(s)).join('')}</div></div>`;
328328
}
329-
html += `<div class="card"><div class="card-header" style="display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px"><div class="card-title">🕐 Recent Scans ${filteredRecent.length !== recent_scans.length ? `<span style="font-size:12px;color:var(--text-muted);font-weight:400;margin-left:8px">(${filteredRecent.length} filtered)</span>` : ''}</div><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center"><button type="button" class="btn btn-ghost" style="font-size:12px;padding:6px 12px" onclick="deleteSelectedScans()">Delete selected</button><button type="button" class="btn btn-ghost" style="font-size:12px;padding:6px 12px;color:var(--accent-red);border-color:rgba(248,113,113,.35)" onclick="clearAllScans()">Clear all</button></div></div><div class="card-body">`;
329+
html += `<div class="card"><div class="card-header" style="display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px"><div class="card-title">🕐 Recent Scans ${filteredRecent.length !== recent_scans.length ? `<span style="font-size:12px;color:var(--text-muted);font-weight:400;margin-left:8px">(${filteredRecent.length} filtered)</span>` : ''}</div><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center"><button type="button" class="btn btn-ghost" style="font-size:12px;padding:6px 12px" onclick="deleteSelectedScans()">Delete selected</button><button type="button" class="btn btn-ghost" style="font-size:12px;padding:6px 12px;color:#f59e0b;border-color:rgba(245,158,11,.35)" onclick="deleteScansNoFindings()">Delete no findings</button><button type="button" class="btn btn-ghost" style="font-size:12px;padding:6px 12px;color:var(--accent-red);border-color:rgba(248,113,113,.35)" onclick="clearAllScans()">Clear all</button></div></div><div class="card-body">`;
330330
if (!filteredRecent.length && !filteredActive.length) {
331331
if (sUI.search || sUI.typeFilter !== 'all' || sUI.statusFilter !== 'all') html += window.emptyState('🔍', 'No matches found', 'Adjust your filters or search term to see more scans.');
332332
else html += scanErr ? window.emptyState('⚠️', 'Scans unavailable', 'Fix the error above or check that the API is reachable.') : window.emptyState('📋', 'No scans yet', 'Start a scan from the launcher above or via the CLI.');

0 commit comments

Comments
 (0)