Changes Made
1. app/web/utils/validators.py - Added 'finalizing' to valid_statuses list
2. app/web/models.py - Updated status field comment to document all valid statuses
3. app/web/jobs/scan_job.py
- Added transition to 'finalizing' status before output file generation
- Sets current_phase = 'generating_outputs' during this phase
- Wrapped output generation in try-except with proper error handling
- If output generation fails, scan is marked 'completed' with warning message (scan data is still valid)
4. app/web/api/scans.py
- Added _recover_orphaned_scan() helper function for smart recovery
- Modified stop_running_scan() to:
- Allow stopping scans with status 'running' OR 'finalizing'
- When scanner not in registry, perform smart recovery instead of returning 404
- Smart recovery checks for output files and marks as 'completed' if found, 'cancelled' if not
5. app/web/services/scan_service.py
- Enhanced cleanup_orphaned_scans() with smart recovery logic
- Now finds scans in both 'running' and 'finalizing' status
- Returns dict with stats: {'recovered': N, 'failed': N, 'total': N}
6. app/web/app.py - Updated caller to handle new dict return type from cleanup_orphaned_scans()
Expected Behavior Now
1. Normal scan flow: running → finalizing → completed
2. Stop on active scan: Sends cancel signal, becomes 'cancelled'
3. Stop on orphaned scan with files: Smart recovery → 'completed'
4. Stop on orphaned scan without files: → 'cancelled'
5. App restart with orphans: Startup cleanup uses smart recovery
This commit is contained in:
@@ -240,14 +240,47 @@ def execute_scan(scan_id: int, config_id: int, db_url: str = None):
|
||||
scan_duration = (end_time - start_time).total_seconds()
|
||||
logger.info(f"Scan {scan_id}: Scanner completed in {scan_duration:.2f} seconds")
|
||||
|
||||
# Generate output files (JSON, HTML, ZIP)
|
||||
logger.info(f"Scan {scan_id}: Generating output files...")
|
||||
output_paths = scanner.generate_outputs(report, timestamp)
|
||||
# Transition to 'finalizing' status before output generation
|
||||
try:
|
||||
scan = session.query(Scan).filter_by(id=scan_id).first()
|
||||
if scan:
|
||||
scan.status = 'finalizing'
|
||||
scan.current_phase = 'generating_outputs'
|
||||
session.commit()
|
||||
logger.info(f"Scan {scan_id}: Status changed to 'finalizing'")
|
||||
except Exception as e:
|
||||
logger.error(f"Scan {scan_id}: Failed to update status to finalizing: {e}")
|
||||
session.rollback()
|
||||
|
||||
# Save results to database
|
||||
logger.info(f"Scan {scan_id}: Saving results to database...")
|
||||
scan_service = ScanService(session)
|
||||
scan_service._save_scan_to_db(report, scan_id, status='completed', output_paths=output_paths)
|
||||
# Generate output files (JSON, HTML, ZIP) with error handling
|
||||
output_paths = {}
|
||||
output_generation_failed = False
|
||||
try:
|
||||
logger.info(f"Scan {scan_id}: Generating output files...")
|
||||
output_paths = scanner.generate_outputs(report, timestamp)
|
||||
except Exception as e:
|
||||
output_generation_failed = True
|
||||
logger.error(f"Scan {scan_id}: Output generation failed: {str(e)}")
|
||||
logger.error(f"Scan {scan_id}: Traceback:\n{traceback.format_exc()}")
|
||||
# Still mark scan as completed with warning since scan data is valid
|
||||
try:
|
||||
scan = session.query(Scan).filter_by(id=scan_id).first()
|
||||
if scan:
|
||||
scan.status = 'completed'
|
||||
scan.error_message = f"Scan completed but output file generation failed: {str(e)}"
|
||||
scan.completed_at = datetime.utcnow()
|
||||
if scan.started_at:
|
||||
scan.duration = (datetime.utcnow() - scan.started_at).total_seconds()
|
||||
session.commit()
|
||||
logger.info(f"Scan {scan_id}: Marked as completed with output generation warning")
|
||||
except Exception as db_error:
|
||||
logger.error(f"Scan {scan_id}: Failed to update status after output error: {db_error}")
|
||||
|
||||
# Save results to database (only if output generation succeeded)
|
||||
if not output_generation_failed:
|
||||
logger.info(f"Scan {scan_id}: Saving results to database...")
|
||||
scan_service = ScanService(session)
|
||||
scan_service._save_scan_to_db(report, scan_id, status='completed', output_paths=output_paths)
|
||||
|
||||
# Evaluate alert rules
|
||||
logger.info(f"Scan {scan_id}: Evaluating alert rules...")
|
||||
|
||||
Reference in New Issue
Block a user