Fix scan output file paths and improve notification system

- Save JSON/HTML/ZIP paths to database when scans complete
- Remove orphaned scan-config-id reference causing JS errors
- Add showAlert function to scan_detail.html and scans.html
- Increase notification z-index to 9999 for modal visibility
- Replace inline alert creation with consistent toast notifications
This commit is contained in:
2025-11-20 08:41:02 -06:00
parent 73d04cae5e
commit 7437716613
5 changed files with 58 additions and 24 deletions

View File

@@ -77,12 +77,12 @@ def execute_scan(scan_id: int, config_id: int, db_url: str = None):
# Generate output files (JSON, HTML, ZIP) # Generate output files (JSON, HTML, ZIP)
logger.info(f"Scan {scan_id}: Generating output files...") logger.info(f"Scan {scan_id}: Generating output files...")
scanner.generate_outputs(report, timestamp) output_paths = scanner.generate_outputs(report, timestamp)
# Save results to database # Save results to database
logger.info(f"Scan {scan_id}: Saving results to database...") logger.info(f"Scan {scan_id}: Saving results to database...")
scan_service = ScanService(session) scan_service = ScanService(session)
scan_service._save_scan_to_db(report, scan_id, status='completed') scan_service._save_scan_to_db(report, scan_id, status='completed', output_paths=output_paths)
# Evaluate alert rules # Evaluate alert rules
logger.info(f"Scan {scan_id}: Evaluating alert rules...") logger.info(f"Scan {scan_id}: Evaluating alert rules...")

View File

@@ -308,7 +308,7 @@ class ScanService:
return count return count
def _save_scan_to_db(self, report: Dict[str, Any], scan_id: int, def _save_scan_to_db(self, report: Dict[str, Any], scan_id: int,
status: str = 'completed') -> None: status: str = 'completed', output_paths: Dict = None) -> None:
""" """
Save scan results to database. Save scan results to database.
@@ -319,6 +319,7 @@ class ScanService:
report: Scan report dictionary from scanner report: Scan report dictionary from scanner
scan_id: Scan ID to update scan_id: Scan ID to update
status: Final scan status (completed or failed) status: Final scan status (completed or failed)
output_paths: Dictionary with paths to generated files {'json': Path, 'html': Path, 'zip': Path}
""" """
scan = self.db.query(Scan).filter(Scan.id == scan_id).first() scan = self.db.query(Scan).filter(Scan.id == scan_id).first()
if not scan: if not scan:
@@ -329,6 +330,15 @@ class ScanService:
scan.duration = report.get('scan_duration') scan.duration = report.get('scan_duration')
scan.completed_at = datetime.utcnow() scan.completed_at = datetime.utcnow()
# Save output file paths
if output_paths:
if 'json' in output_paths:
scan.json_path = str(output_paths['json'])
if 'html' in output_paths:
scan.html_path = str(output_paths['html'])
if 'zip' in output_paths:
scan.zip_path = str(output_paths['zip'])
# Map report data to database models # Map report data to database models
self._map_report_to_models(report, scan) self._map_report_to_models(report, scan)

View File

@@ -108,7 +108,7 @@
</div> </div>
<!-- Global notification container - always above modals --> <!-- Global notification container - always above modals -->
<div id="notification-container" style="position: fixed; top: 20px; right: 20px; z-index: 1100; min-width: 300px;"></div> <div id="notification-container" style="position: fixed; top: 20px; right: 20px; z-index: 9999; min-width: 300px;"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}

View File

@@ -162,6 +162,25 @@
let scanData = null; let scanData = null;
let historyChart = null; // Store chart instance to prevent duplicates let historyChart = null; // Store chart instance to prevent duplicates
// Show alert notification
function showAlert(type, message) {
const container = document.getElementById('notification-container');
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show mb-2`;
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
container.appendChild(notification);
// Auto-dismiss after 5 seconds
setTimeout(() => {
notification.remove();
}, 5000);
}
// Load scan on page load // Load scan on page load
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
loadScan().then(() => { loadScan().then(() => {
@@ -218,7 +237,6 @@
document.getElementById('scan-timestamp').textContent = new Date(scan.timestamp).toLocaleString(); document.getElementById('scan-timestamp').textContent = new Date(scan.timestamp).toLocaleString();
document.getElementById('scan-duration').textContent = scan.duration ? `${scan.duration.toFixed(1)}s` : '-'; document.getElementById('scan-duration').textContent = scan.duration ? `${scan.duration.toFixed(1)}s` : '-';
document.getElementById('scan-triggered-by').textContent = scan.triggered_by || 'manual'; document.getElementById('scan-triggered-by').textContent = scan.triggered_by || 'manual';
document.getElementById('scan-config-id').textContent = scan.config_id || '-';
// Status badge // Status badge
let statusBadge = ''; let statusBadge = '';
@@ -439,7 +457,7 @@
window.location.href = '{{ url_for("main.scans") }}'; window.location.href = '{{ url_for("main.scans") }}';
} catch (error) { } catch (error) {
console.error('Error deleting scan:', error); console.error('Error deleting scan:', error);
alert(`Failed to delete scan: ${error.message}`); showAlert('danger', `Failed to delete scan: ${error.message}`);
// Re-enable button on error // Re-enable button on error
deleteBtn.disabled = false; deleteBtn.disabled = false;

View File

@@ -151,6 +151,25 @@
let statusFilter = ''; let statusFilter = '';
let totalCount = 0; let totalCount = 0;
// Show alert notification
function showAlert(type, message) {
const container = document.getElementById('notification-container');
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show mb-2`;
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
container.appendChild(notification);
// Auto-dismiss after 5 seconds
setTimeout(() => {
notification.remove();
}, 5000);
}
// Load initial data when page loads // Load initial data when page loads
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
loadScans(); loadScans();
@@ -456,15 +475,7 @@
bootstrap.Modal.getInstance(document.getElementById('triggerScanModal')).hide(); bootstrap.Modal.getInstance(document.getElementById('triggerScanModal')).hide();
// Show success message // Show success message
const alertDiv = document.createElement('div'); showAlert('success', `Scan triggered successfully! (ID: ${data.scan_id})`);
alertDiv.className = 'alert alert-success alert-dismissible fade show mt-3';
alertDiv.innerHTML = `
Scan triggered successfully! (ID: ${data.scan_id})
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// Insert at the beginning of container-fluid
const container = document.querySelector('.container-fluid');
container.insertBefore(alertDiv, container.firstChild);
// Refresh scans // Refresh scans
loadScans(); loadScans();
@@ -490,23 +501,18 @@
}); });
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to delete scan'); const data = await response.json();
throw new Error(data.message || 'Failed to delete scan');
} }
// Show success message // Show success message
const alertDiv = document.createElement('div'); showAlert('success', `Scan ${scanId} deleted successfully.`);
alertDiv.className = 'alert alert-success alert-dismissible fade show mt-3';
alertDiv.innerHTML = `
Scan ${scanId} deleted successfully.
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.querySelector('.container-fluid').insertBefore(alertDiv, document.querySelector('.row'));
// Refresh scans // Refresh scans
loadScans(); loadScans();
} catch (error) { } catch (error) {
console.error('Error deleting scan:', error); console.error('Error deleting scan:', error);
alert('Failed to delete scan. Please try again.'); showAlert('danger', `Failed to delete scan: ${error.message}`);
} }
} }