feat: re-run and delete scan from detail page

Backend:
- DELETE /api/v1/scans/{id} — 204 on success, 404 if missing,
  409 if pending/running; CASCADE removes routes and flights

Frontend (api.ts):
- scanApi.delete(id)

Frontend (ScanDetails.tsx):
- Re-run button: derives window_months from stored dates, detects
  country vs airports mode via comma in scan.country, creates new
  scan and navigates to it; disabled while scan is active
- Delete button: inline two-step confirm (no modal), navigates to
  dashboard on success; disabled while scan is active

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 16:33:45 +01:00
parent f9411edd3c
commit 4926e89e46
3 changed files with 125 additions and 0 deletions

View File

@@ -1337,6 +1337,42 @@ async def get_scan_status(scan_id: int):
)
@router_v1.delete("/scans/{scan_id}", status_code=204)
async def delete_scan(scan_id: int):
"""
Delete a scan and all its associated routes and flights (CASCADE).
Returns 409 if the scan is currently running or pending.
"""
try:
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT status FROM scans WHERE id = ?", (scan_id,))
row = cursor.fetchone()
if not row:
conn.close()
raise HTTPException(status_code=404, detail=f"Scan not found: {scan_id}")
if row[0] in ('pending', 'running'):
conn.close()
raise HTTPException(
status_code=409,
detail="Cannot delete a scan that is currently pending or running."
)
cursor.execute("DELETE FROM scans WHERE id = ?", (scan_id,))
conn.commit()
conn.close()
logging.info(f"Scan {scan_id} deleted")
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to delete scan: {str(e)}")
@router_v1.get("/scans/{scan_id}/routes", response_model=PaginatedResponse[Route])
async def get_scan_routes(
scan_id: int,