diff --git a/aiScanner/api.py b/aiScanner/api.py index 441cb9de3..d10cbfa40 100644 --- a/aiScanner/api.py +++ b/aiScanner/api.py @@ -560,6 +560,33 @@ def scan_callback(request): scan_record.save() + # Also update the ScanStatusUpdate record with final statistics + try: + from .status_models import ScanStatusUpdate + status_update, _ = ScanStatusUpdate.objects.get_or_create(scan_id=scan_id) + status_update.phase = 'completed' + status_update.progress = 100 + status_update.files_discovered = summary.get('files_scanned', 0) # Use files_scanned as approximation + status_update.files_scanned = summary.get('files_scanned', 0) + status_update.files_remaining = 0 + status_update.threats_found = summary.get('total_findings', 0) + # Extract critical and high threats from findings if available + critical_count = 0 + high_count = 0 + for finding in findings: + severity = finding.get('severity', '').lower() + if severity == 'critical': + critical_count += 1 + elif severity == 'high': + high_count += 1 + status_update.critical_threats = critical_count + status_update.high_threats = high_count + status_update.activity_description = f"Scan completed - {summary.get('total_findings', 0)} threats found" + status_update.save() + logging.writeToFile(f"[API] Updated ScanStatusUpdate for completed scan {scan_id}") + except Exception as e: + logging.writeToFile(f"[API] Error updating ScanStatusUpdate: {str(e)}") + # Update user balance if scan cost money if scan_record.cost_usd > 0: try: diff --git a/aiScanner/templates/aiScanner/scanner.html b/aiScanner/templates/aiScanner/scanner.html index a2ab60495..d63d2af22 100644 --- a/aiScanner/templates/aiScanner/scanner.html +++ b/aiScanner/templates/aiScanner/scanner.html @@ -718,9 +718,16 @@ AI Security Scanner - CyberPanel {% endif %} - +
+ + {% if scan.status == 'completed' %} + + {% endif %} +
{% endfor %} @@ -953,9 +960,16 @@ function updateScanHistoryTable(scans) { `${scan.issues_found}` : `${scan.issues_found}`; - const actionsHtml = ``; + const actionsHtml = `
+ + ${scan.status === 'completed' ? ` + + ` : ''} +
`; const progressHtml = (scan.status === 'running' || scan.status === 'pending') ? `
@@ -1011,6 +1025,24 @@ function viewScanDetails(scanId) { fetchLiveProgress(scanId); } +function viewOnPlatform(scanId) { + // Fetch the platform monitor URL + fetch(`/aiscanner/platform-monitor-url/${scanId}/`) + .then(response => response.json()) + .then(data => { + if (data.success && data.monitor_url) { + // Open in new tab + window.open(data.monitor_url, '_blank'); + } else { + alert('Error: ' + (data.error || 'Could not get platform URL')); + } + }) + .catch(error => { + console.error('Error:', error); + alert('Failed to get platform URL. Please try again.'); + }); +} + function fetchLiveProgress(scanId) { console.log(`[AI Scanner] Fetching live progress for scan: ${scanId}`); fetch(`/api/ai-scanner/scan/${scanId}/live-progress`) @@ -1207,21 +1239,29 @@ function displayCompletedScanDetails(scan) {
`; } + // Use enhanced scan data from ScanStatusUpdate if available + const filesScanned = scan.files_scanned || 0; + const filesDiscovered = scan.files_discovered || scan.files_scanned || 0; + const threatsFound = scan.threats_found !== undefined ? scan.threats_found : (scan.issues_found || 0); + const criticalThreats = scan.critical_threats || 0; + const highThreats = scan.high_threats || 0; + document.getElementById('completedSection').innerHTML = `
-

${scan.files_scanned || 0}

+

${filesScanned}${filesDiscovered > filesScanned ? `/${filesDiscovered}` : ''}

Files Scanned

-
- +
+
-

${scan.issues_found || 0}

-

Issues Found

+

${threatsFound}

+

Threats Found

+ ${(criticalThreats > 0 || highThreats > 0) ? `${criticalThreats > 0 ? `${criticalThreats} Critical` : ''}${criticalThreats > 0 && highThreats > 0 ? ', ' : ''}${highThreats > 0 ? `${highThreats} High` : ''}` : ''}
@@ -1239,6 +1279,11 @@ function displayCompletedScanDetails(scan) {
${findingsHtml} +
+ +
`; } diff --git a/aiScanner/urls.py b/aiScanner/urls.py index 79f117664..6cc9784aa 100644 --- a/aiScanner/urls.py +++ b/aiScanner/urls.py @@ -15,6 +15,7 @@ urlpatterns = [ # Scan management path('scan-history/', views.getScanHistory, name='aiScannerHistory'), path('scan-details//', views.getScanDetails, name='aiScannerDetails'), + path('platform-monitor-url//', views.getPlatformMonitorUrl, name='aiScannerPlatformMonitorUrl'), path('platform-status//', views.getPlatformScanStatus, name='aiScannerPlatformStatus'), # Note: RESTful API endpoints are in /api/urls.py for external access diff --git a/aiScanner/views.py b/aiScanner/views.py index f33059880..c16c0a245 100644 --- a/aiScanner/views.py +++ b/aiScanner/views.py @@ -129,12 +129,14 @@ def getScanHistory(request): return JsonResponse({'success': False, 'error': str(e)}) +@require_http_methods(['GET']) def getScanDetails(request, scan_id): """Get detailed scan results""" try: userID = request.session['userID'] from loginSystem.models import Administrator from .models import ScanHistory + from .status_models import ScanStatusUpdate from plogical.acl import ACLManager admin = Administrator.objects.get(pk=userID) @@ -153,6 +155,23 @@ def getScanDetails(request, scan_id): except ScanHistory.DoesNotExist: return JsonResponse({'success': False, 'error': 'Scan not found'}) + # Get the status update for more detailed information + try: + status_update = ScanStatusUpdate.objects.get(scan_id=scan_id) + # Use detailed information from status update if available + files_scanned = status_update.files_scanned if status_update.files_scanned > 0 else scan.files_scanned + files_discovered = status_update.files_discovered + threats_found = status_update.threats_found + critical_threats = status_update.critical_threats + high_threats = status_update.high_threats + except ScanStatusUpdate.DoesNotExist: + # Fall back to basic information from scan history + files_scanned = scan.files_scanned + files_discovered = scan.files_scanned # Approximate + threats_found = scan.issues_found + critical_threats = 0 + high_threats = 0 + scan_data = { 'scan_id': scan.scan_id, 'domain': scan.domain, @@ -161,8 +180,12 @@ def getScanDetails(request, scan_id): 'started_at': scan.started_at.strftime('%Y-%m-%d %H:%M:%S'), 'completed_at': scan.completed_at.strftime('%Y-%m-%d %H:%M:%S') if scan.completed_at else None, 'cost_usd': float(scan.cost_usd) if scan.cost_usd else 0, - 'files_scanned': scan.files_scanned, + 'files_scanned': files_scanned, + 'files_discovered': files_discovered, 'issues_found': scan.issues_found, + 'threats_found': threats_found, + 'critical_threats': critical_threats, + 'high_threats': high_threats, 'findings': scan.findings, 'summary': scan.summary, 'error_message': scan.error_message @@ -178,6 +201,86 @@ def getScanDetails(request, scan_id): return JsonResponse({'success': False, 'error': str(e)}) +@require_http_methods(['GET']) +def getPlatformMonitorUrl(request, scan_id): + """Get the platform monitor URL for a scan""" + try: + userID = request.session['userID'] + from loginSystem.models import Administrator + from .models import ScanHistory, AIScannerSettings + from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging + import requests + + # Get scan to verify ownership + try: + scan = ScanHistory.objects.get(scan_id=scan_id) + admin = Administrator.objects.get(pk=userID) + + # Verify access + from plogical.acl import ACLManager + currentACL = ACLManager.loadedACL(userID) + if currentACL['admin'] != 1: + user_admins = ACLManager.loadUserObjects(userID) + if scan.admin not in user_admins: + return JsonResponse({'success': False, 'error': 'Access denied'}) + except ScanHistory.DoesNotExist: + return JsonResponse({'success': False, 'error': 'Scan not found'}) + + # Get API key for the scan owner + try: + scanner_settings = scan.admin.ai_scanner_settings + if not scanner_settings.api_key: + return JsonResponse({'success': False, 'error': 'API key not configured'}) + api_key = scanner_settings.api_key + except: + return JsonResponse({'success': False, 'error': 'Scanner not configured'}) + + # Call platform API to get monitor URL + try: + url = f"https://platform.cyberpersons.com/ai-scanner/api/ai-scanner/scan/{scan_id}/monitor-url/" + headers = { + 'Authorization': f'Bearer {api_key}', + 'Content-Type': 'application/json' + } + + logging.writeToFile(f"[AI Scanner] Fetching platform monitor URL for scan {scan_id}") + + response = requests.get(url, headers=headers, timeout=10) + data = response.json() + + if response.status_code == 200 and data.get('success'): + logging.writeToFile(f"[AI Scanner] Got monitor URL: {data.get('monitor_url')}") + return JsonResponse({ + 'success': True, + 'monitor_url': data.get('monitor_url'), + 'platform_scan_id': data.get('platform_scan_id') + }) + else: + error_msg = data.get('error', 'Failed to get monitor URL') + logging.writeToFile(f"[AI Scanner] Failed to get monitor URL: {error_msg}") + return JsonResponse({ + 'success': False, + 'error': error_msg, + 'scan_exists': data.get('scan_exists', False) + }) + + except requests.exceptions.Timeout: + logging.writeToFile(f"[AI Scanner] Platform request timeout for scan {scan_id}") + return JsonResponse({'success': False, 'error': 'Platform request timeout'}) + except requests.exceptions.RequestException as e: + logging.writeToFile(f"[AI Scanner] Platform request error: {str(e)}") + return JsonResponse({'success': False, 'error': f'Platform error: {str(e)}'}) + except Exception as e: + logging.writeToFile(f"[AI Scanner] Unexpected error: {str(e)}") + return JsonResponse({'success': False, 'error': f'Error: {str(e)}'}) + + except KeyError: + return JsonResponse({'success': False, 'error': 'Not authenticated'}) + except Exception as e: + logging.writeToFile(f"[AI Scanner] getPlatformMonitorUrl error: {str(e)}") + return JsonResponse({'success': False, 'error': str(e)}) + + def getPlatformScanStatus(request, scan_id): """Get real-time scan status from AI Scanner platform""" try: