hot fixes for several UI and logic issues

This commit is contained in:
2025-11-17 15:41:51 -06:00
parent 5f2314a532
commit 72c4f3d29b
13 changed files with 233 additions and 93 deletions

View File

@@ -301,26 +301,71 @@ class ConfigService:
def delete_config(self, filename: str) -> None:
"""
Delete config file if not used by schedules.
Delete config file and cascade delete any associated schedules.
When a config is deleted, all schedules using that config (both enabled
and disabled) are automatically deleted as well, since they would be
invalid without the config file.
Args:
filename: Config filename to delete
Raises:
FileNotFoundError: If config doesn't exist
ValueError: If config used by active schedules
"""
filepath = os.path.join(self.configs_dir, filename)
if not os.path.exists(filepath):
raise FileNotFoundError(f"Config file '{filename}' not found")
# Check if used by schedules
schedules = self.get_schedules_using_config(filename)
if schedules:
schedule_list = ', '.join(schedules)
raise ValueError(
f"Cannot delete config '{filename}' because it is used by the following schedules: {schedule_list}"
# Delete any schedules using this config (both enabled and disabled)
try:
from web.services.schedule_service import ScheduleService
from flask import current_app
# Get database session from Flask app
db = current_app.db_session
# Get all schedules
schedule_service = ScheduleService(db)
result = schedule_service.list_schedules(page=1, per_page=10000)
schedules = result.get('schedules', [])
# Build full path for comparison
config_path = os.path.join(self.configs_dir, filename)
# Find and delete all schedules using this config (enabled or disabled)
deleted_schedules = []
for schedule in schedules:
schedule_config = schedule.get('config_file', '')
# Handle both absolute paths and just filenames
if schedule_config == filename or schedule_config == config_path:
schedule_id = schedule.get('id')
schedule_name = schedule.get('name', 'Unknown')
try:
schedule_service.delete_schedule(schedule_id)
deleted_schedules.append(schedule_name)
except Exception as e:
import logging
logging.getLogger(__name__).warning(
f"Failed to delete schedule {schedule_id} ('{schedule_name}'): {e}"
)
if deleted_schedules:
import logging
logging.getLogger(__name__).info(
f"Cascade deleted {len(deleted_schedules)} schedule(s) associated with config '{filename}': {', '.join(deleted_schedules)}"
)
except ImportError:
# If ScheduleService doesn't exist yet, skip schedule deletion
pass
except Exception as e:
# Log error but continue with config deletion
import logging
logging.getLogger(__name__).error(
f"Error deleting schedules for config {filename}: {e}", exc_info=True
)
# Delete file
@@ -404,30 +449,42 @@ class ConfigService:
# Import here to avoid circular dependency
try:
from web.services.schedule_service import ScheduleService
schedule_service = ScheduleService()
from flask import current_app
# Get all schedules
schedules = schedule_service.list_schedules()
# Get database session from Flask app
db = current_app.db_session
# Get all schedules (use large per_page to get all)
schedule_service = ScheduleService(db)
result = schedule_service.list_schedules(page=1, per_page=10000)
# Extract schedules list from paginated result
schedules = result.get('schedules', [])
# Build full path for comparison
config_path = os.path.join(self.configs_dir, filename)
# Find schedules using this config
# Find schedules using this config (only enabled schedules)
using_schedules = []
for schedule in schedules:
schedule_config = schedule.get('config_file', '')
# Handle both absolute paths and just filenames
if schedule_config == filename or schedule_config == config_path:
using_schedules.append(schedule.get('name', 'Unknown'))
# Only count enabled schedules
if schedule.get('enabled', False):
using_schedules.append(schedule.get('name', 'Unknown'))
return using_schedules
except ImportError:
# If ScheduleService doesn't exist yet, return empty list
return []
except Exception:
except Exception as e:
# If any error occurs, return empty list (safer than failing)
# Log the error for debugging
import logging
logging.getLogger(__name__).error(f"Error getting schedules using config {filename}: {e}", exc_info=True)
return []
def generate_filename_from_title(self, title: str) -> str:

View File

@@ -561,6 +561,7 @@ class ScanService:
'duration': scan.duration,
'status': scan.status,
'title': scan.title,
'config_file': scan.config_file,
'triggered_by': scan.triggered_by,
'created_at': scan.created_at.isoformat() if scan.created_at else None
}
@@ -675,6 +676,8 @@ class ScanService:
{
'scan1': {...}, # Scan 1 summary
'scan2': {...}, # Scan 2 summary
'same_config': bool, # Whether both scans used the same config
'config_warning': str | None, # Warning message if configs differ
'ports': {
'added': [...],
'removed': [...],
@@ -700,6 +703,22 @@ class ScanService:
if not scan1 or not scan2:
return None
# Check if scans use the same configuration
config1 = scan1.get('config_file', '')
config2 = scan2.get('config_file', '')
same_config = (config1 == config2) and (config1 != '')
# Generate warning message if configs differ
config_warning = None
if not same_config:
config_warning = (
f"These scans use different configurations. "
f"Scan #{scan1_id} used '{config1 or 'unknown'}' and "
f"Scan #{scan2_id} used '{config2 or 'unknown'}'. "
f"The comparison may show all changes as additions/removals if the scans "
f"cover different IP ranges or infrastructure."
)
# Extract port data
ports1 = self._extract_ports_from_scan(scan1)
ports2 = self._extract_ports_from_scan(scan2)
@@ -733,14 +752,18 @@ class ScanService:
'id': scan1['id'],
'timestamp': scan1['timestamp'],
'title': scan1['title'],
'status': scan1['status']
'status': scan1['status'],
'config_file': config1
},
'scan2': {
'id': scan2['id'],
'timestamp': scan2['timestamp'],
'title': scan2['title'],
'status': scan2['status']
'status': scan2['status'],
'config_file': config2
},
'same_config': same_config,
'config_warning': config_warning,
'ports': ports_comparison,
'services': services_comparison,
'certificates': certificates_comparison,