diff --git a/frontend/src/pages/Scans.tsx b/frontend/src/pages/Scans.tsx index 143319a7..006f1421 100644 --- a/frontend/src/pages/Scans.tsx +++ b/frontend/src/pages/Scans.tsx @@ -482,15 +482,34 @@ export default function Scans() { {formatLocaleTime(createDate)}

- {task.duration_seconds && ( -
-

- {formatDuration( - task.duration_seconds, - )?.toUpperCase()} -

-
- )} +
+ {task.duration_seconds && ( +
+

+ {formatDuration( + task.duration_seconds, + )?.toUpperCase()} +

+
+ )} + {(task.status === "completed" || + task.status === "failed" || + task.status === "cancelled") && ( + + )} +
diff --git a/frontend/testing/unit/pages/Scans.test.tsx b/frontend/testing/unit/pages/Scans.test.tsx index 994f3658..ca7d083c 100644 --- a/frontend/testing/unit/pages/Scans.test.tsx +++ b/frontend/testing/unit/pages/Scans.test.tsx @@ -12,6 +12,7 @@ vi.mock('../../../src/api', () => ({ deleteTask: vi.fn().mockResolvedValue({}), clearAllTasks: vi.fn().mockResolvedValue({}), bulkDeleteTasks: vi.fn().mockResolvedValue({}), + startTask: vi.fn().mockResolvedValue({ task_id: 'new-task-123' }), })) vi.mock('../../../src/routes', () => ({ @@ -213,4 +214,28 @@ describe('Scans — task list', () => { await waitFor(() => expect(screen.getByText('Delete Scan Record')).toBeInTheDocument()) }) + + it('renders quick re-run button for completed tasks and triggers handleRescan', async () => { + const tasks = [makeTask({ task_id: 'task-123', status: 'completed', tool: 'nmap' })] + mockFetch(tasks) + renderScans() + + await waitFor(() => expect(screen.getByText('nmap')).toBeInTheDocument()) + + const rerunBtn = screen.getByRole('button', { name: /Re-run nmap scan/i }) + expect(rerunBtn).toBeInTheDocument() + + const { startTask } = await import('../../../src/api') + await userEvent.click(rerunBtn) + + await waitFor(() => { + expect(startTask).toHaveBeenCalledWith( + 'nmap', + expect.any(Object), + true, + undefined, + undefined + ) + }) + }) })