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: