feat(ui): add 'Cancel all failed' button to Jobs screen

Pair with the existing 'Retry all failed'. Drops every failed job from
the queue at once. Single confirm prompt. Optimistic local update so the
list clears instantly instead of waiting for the 5s poll tick.

.jobs-cancel-all CSS tinted danger-red without being a loud .btn danger,
matching the per-row Cancel pattern.
This commit is contained in:
Zac Gaetano 2026-05-29 00:02:55 +00:00
parent 303f12e0f9
commit 6f64b55824
2 changed files with 29 additions and 3 deletions

View file

@ -108,6 +108,23 @@ function Jobs({ navigate }) {
).then(refresh);
}, [jobs, refresh]);
// Drop every failed job from the queue. The opposite of Retry all used
// when a batch of jobs is unrecoverable (e.g. assets that were deleted
// mid-encode) and the operator just wants the queue cleared.
const handleCancelAll = React.useCallback(() => {
const failedJobs = jobs.filter(j => j.status === 'failed');
if (failedJobs.length === 0) return;
if (!window.confirm(`Remove all ${failedJobs.length} failed jobs from the queue?\nThis cannot be undone.`)) return;
Promise.allSettled(
failedJobs.map(j => window.ZAMPP_API.fetch('/jobs/' + j.id, { method: 'DELETE' }))
).then(() => {
// Optimistic local drop so the UI updates the instant the modal closes,
// not 5s later on the next poll tick.
setJobs(prev => prev.filter(j => j.status !== 'failed'));
refresh();
});
}, [jobs, refresh]);
const counts = {
all: jobs.length,
running: jobs.filter(j => j.status === 'running').length,
@ -124,9 +141,14 @@ function Jobs({ navigate }) {
<span className="subtitle">Proxy generation, transcoding, and processing queue</span>
<div className="spacer" />
{counts.failed > 0 && (
<button className="btn ghost sm" onClick={handleRetryAll} title={`Retry all ${counts.failed} failed jobs`}>
<Icon name="refresh" />Retry all failed
</button>
<>
<button className="btn ghost sm" onClick={handleRetryAll} title={`Retry all ${counts.failed} failed jobs`}>
<Icon name="refresh" />Retry all failed
</button>
<button className="btn ghost sm jobs-cancel-all" onClick={handleCancelAll} title={`Remove all ${counts.failed} failed jobs from the queue`}>
<Icon name="x" />Cancel all failed
</button>
</>
)}
<button className="btn ghost sm" onClick={refresh}>
<Icon name="refresh" />Refresh

View file

@ -1372,3 +1372,7 @@
display: flex; gap: 8px; margin-top: 4px;
}
.premiere-release-actions a { text-decoration: none; }
/* Tint Cancel-all-failed button to signal destructive action without
making it loud same pattern as the per-row Cancel. */
.jobs-cancel-all { color: var(--danger); }