diff --git a/app/web_routes.py b/app/web_routes.py index ffb04a6..093dc63 100644 --- a/app/web_routes.py +++ b/app/web_routes.py @@ -7,6 +7,7 @@ from pathlib import Path import io import csv +import re from typing import Union, Optional # KORREKTUR: Union und Optional importieren from app.database import get_db @@ -65,8 +66,21 @@ async def web_laptops_overview(request: Request, db: Session = Depends(get_db), hours_since = time_since_last_scan.total_seconds() / 3600.0 hours_rounded = round(hours_since) - if laptop_instance.last_scan_threats_found is True: + # Retroactively fix old DB entries where Event 1002 was marked as a threat + is_real_threat = laptop_instance.last_scan_threats_found + is_error = False + if laptop_instance.last_scan_result_message and ("Event 1002" in laptop_instance.last_scan_result_message or "FEHLER:" in laptop_instance.last_scan_result_message or "stopped" in laptop_instance.last_scan_result_message or "Fehler" in laptop_instance.last_scan_result_message): + if "Event 1002" in laptop_instance.last_scan_result_message: + is_real_threat = False # 1002 is just a cancelled scan, not a threat + is_error = True + + laptop_instance.last_scan_threats_found = is_real_threat + laptop_instance.is_error = is_error + + if is_real_threat is True: status_info = {"text": "Bedrohung(en) gefunden!", "color_class": "status-red", "style": ""} + elif is_error: + status_info = {"text": "Fehler / Abbruch", "color_class": "status-yellow", "style": ""} else: if hours_since <= 5: hue = 120 # Green @@ -90,11 +104,17 @@ async def web_laptops_overview(request: Request, db: Session = Depends(get_db), simplified_result_message = "N/A" if laptop_instance.last_scan_result_message: - msg = laptop_instance.last_scan_result_message - if "erfolgreich abgeschlossen" in msg: + # Clean up old pseudo-localization tokens from database + clean_msg = laptop_instance.last_scan_result_message + clean_msg = re.sub(r'%[nиñńηйNИÑŃΗЙ]', '\n', clean_msg) + clean_msg = re.sub(r'%[tтŧťτTТŦŤΤ]', ' ', clean_msg) + clean_msg = re.sub(r'%[bьвβBЬВΒ]', '', clean_msg) + laptop_instance.last_scan_result_message = clean_msg + + if "erfolgreich abgeschlossen" in clean_msg: simplified_result_message = "OK" else: - simplified_result_message = msg[:30] + ("..." if len(msg) > 30 else "") + simplified_result_message = clean_msg[:30] + ("..." if len(clean_msg) > 30 else "") has_error = False @@ -186,6 +206,15 @@ async def export_daily_report_csv(request: Request, report_date_str: Optional[st if len(scan_result) > 50: scan_result = scan_result[:50] + "..." threats_str = "Nein" + + # Clean up old pseudo-localization tokens from database + threats_str = re.sub(r'%[nиñńηйNИÑŃΗЙ]', '\n', threats_str) + threats_str = re.sub(r'%[tтŧťτTТŦŤΤ]', ' ', threats_str) + threats_str = re.sub(r'%[bьвβBЬВΒ]', '', threats_str) + + scan_result = re.sub(r'%[nиñńηйNИÑŃΗЙ]', '\n', scan_result) + scan_result = re.sub(r'%[tтŧťτTТŦŤΤ]', ' ', scan_result) + scan_result = re.sub(r'%[bьвβBЬВΒ]', '', scan_result) writer.writerow([laptop.alias_name, laptop.hostname, scan_time_str, scan_result, threats_str]) @@ -253,6 +282,9 @@ async def web_daily_report(request: Request, report_date_str: Optional[str] = No is_real_threat = False # 1002 is just a cancelled scan, not a threat is_error = True + laptop.last_scan_threats_found = is_real_threat + laptop.is_error = is_error + if is_real_threat is True: if getattr(laptop, "last_scan_threat_details", None): status_text = laptop.last_scan_threat_details @@ -270,6 +302,16 @@ async def web_daily_report(request: Request, report_date_str: Optional[str] = No status_text, color_class = "OK (Scan <24h)", "status-green" else: status_text, color_class = "OK (Scan älter)", "status-yellow" + + # Clean up old pseudo-localization tokens from database + if status_text: + status_text = re.sub(r'%[nиñńηйNИÑŃΗЙ]', '\n', status_text) + status_text = re.sub(r'%[tтŧťτTТŦŤΤ]', ' ', status_text) + status_text = re.sub(r'%[bьвβBЬВΒ]', '', status_text) + + # Truncate very long texts if they aren't threats or errors to save space + if not is_real_threat and not is_error and len(status_text) > 100: + status_text = status_text[:100] + "..." else: status_text, color_class = "Kein Scan bisher", "status-white" diff --git a/client/ScanOpClient.ps1 b/client/ScanOpClient.ps1 index 5ca664d..a71e0a2 100644 --- a/client/ScanOpClient.ps1 +++ b/client/ScanOpClient.ps1 @@ -94,7 +94,7 @@ while ($true) { function Send-ScanReport { param( [Parameter(Mandatory = $true)][string]$ScanTime, [Parameter(Mandatory = $true)][string]$ScanType, [Parameter(Mandatory = $true)][string]$ScanResultMessage, [Parameter(Mandatory = $true)][bool]$ThreatsFound, [string]$ThreatDetails = $null ); Write-Log -Message "Bereite Scan-Bericht ($ScanType) für Versand vor."; if ([string]::IsNullOrWhiteSpace($ScanTime)) { $ScanTime = (Get-Date "1970-01-01").ToUniversalTime().ToString("o") } ; if ([string]::IsNullOrWhiteSpace($ScanType)) { $ScanType = "Unbekannt" } ; if ([string]::IsNullOrWhiteSpace($ScanResultMessage)) { $ScanResultMessage = "Keine Meldung" } ; $CleanResultMessage = $ScanResultMessage -replace '[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '' ; $CleanThreatDetails = if ($ThreatDetails) { $ThreatDetails -replace '[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '' } else { $null } ; $payloadContent = @{ laptop_identifier = $AliasName; client_scan_time = $ScanTime; scan_type = $ScanType; scan_result_message = $CleanResultMessage; threats_found = $ThreatsFound }; if ($null -ne $CleanThreatDetails -and (-not [string]::IsNullOrWhiteSpace($CleanThreatDetails))) { $payloadContent.threat_details = $CleanThreatDetails } else { $payloadContent.threat_details = $null } ; $payloadBodyJson = $payloadContent | ConvertTo-Json -Depth 5 -Compress; $utf8Encoding = [System.Text.Encoding]::UTF8; $payloadBytes = $utf8Encoding.GetBytes($payloadBodyJson); $requestHeaders = @{ "Content-Type" = "application/json; charset=utf-8"; "X-API-Key" = $ApiKey }; Write-Log -Message "Sende Bericht... (Länge: $($payloadBytes.Length) bytes)"; $ErrorActionPreferenceBackup = $ErrorActionPreference; $ErrorActionPreference = "Stop"; try { Invoke-RestMethod -Uri $ReportUrl -Method Post -Body $payloadBytes -Headers $requestHeaders -TimeoutSec 120; Write-Log -Message "Scan-Bericht erfolgreich an Server gesendet."; $Global:LastSuccessfulReportTimeUTC = (Get-Date).ToUniversalTime(); try { ($Global:LastSuccessfulReportTimeUTC.ToString("o") | ConvertTo-Json -Compress) | Set-Content -Path $LastReportTimeFilePath -Force -Encoding UTF8; Write-Log -Message "Letzte erfolgreiche Report-Zeit aktualisiert: $($Global:LastSuccessfulReportTimeUTC.ToLocalTime())" } catch { Write-Log -Level WARN -Message "Fehler beim Speichern von '$LastReportTimeFilePath': $($_.Exception.Message)" }; return $true } catch { $CaughtException = $_; Write-Log -Level ERROR -Message "FEHLER bei Send-ScanReport: $($CaughtException.ToString())"; if ($CaughtException.Exception -is [System.Net.WebException] -and $null -ne $CaughtException.Exception.Response) { $webEx = $CaughtException.Exception; $httpResponse = $webEx.Response; $actualHttpStatusCode = [int]$httpResponse.StatusCode; Write-Log -Level ERROR -Message " HTTP Status: $actualHttpStatusCode"; try { $responseStream = $httpResponse.GetResponseStream(); $streamReader = New-Object System.IO.StreamReader($responseStream, [System.Text.Encoding]::UTF8); $errorBodyContent = $streamReader.ReadToEnd(); $streamReader.Close(); $responseStream.Close(); Write-Log -Level ERROR -Message " Fehler-Body vom Server: $errorBodyContent" } catch { Write-Log -Level ERROR -Message " Zusätzlicher Fehler beim Lesen des Fehler-Bodys: $($_.Exception.Message)" } }; return $false } finally { $ErrorActionPreference = $ErrorActionPreferenceBackup } } function ConvertFrom-DefenderEvent { param( [Parameter(Mandatory = $true)] $Event ); $eventTimeUTC = $Event.TimeCreated.ToUniversalTime().ToString("o"); - $cleanMsg = $Event.Message -replace '[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '' -replace '%n', "`n" -replace '%t', " " -replace '%b', "" + $cleanMsg = $Event.Message -replace '[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '' -replace '%[nиñńηйNИÑŃΗЙ]', "`n" -replace '%[tтŧťτTТŦŤΤ]', " " -replace '%[bьвβBЬВΒ]', "" $simplified = @{ Message = "Event $($Event.Id): " + $cleanMsg.Trim(); ThreatsFound = $false; ThreatDetails = $null }; if ($simplified.Message -match "Bedrohung gefunden" -or $simplified.Message -match "Malware found") { $simplified.ThreatsFound = $true }; if ($Event.Id -in (1116, 1117, 1118)) { $simplified.ThreatsFound = $true }; if ($Event.Message -match "(?:Name|Threat Name):\s*(.*?)\s*(?:Pfad|Path|File):\s*(.*?)\s*(?:Aktion|Action):\s*(.*?)(?:\r?\n|$)") { $simplified.ThreatDetails = "Name: $($Matches[1].Trim()), Pfad: $($Matches[2].Trim()), Aktion: $($Matches[3].Trim())" } elseif ($Event.Message -match "(?:Name|Threat Name):\s*(.*?)\s*(?:Pfad|Path|File):\s*(.*?)(?:\r?\n|$)") { $simplified.ThreatDetails = "Name: $($Matches[1].Trim()), Pfad: $($Matches[2].Trim())" }; return [PSCustomObject]$simplified } # --- HAUPT-POLLING-SCHLEIFE --- diff --git a/templates/daily_report.html b/templates/daily_report.html index 84f35dc..46ef33b 100644 --- a/templates/daily_report.html +++ b/templates/daily_report.html @@ -131,6 +131,8 @@