modules/Devolutions.CIEM.PSU/Pages/New-CIEMScanHistoryPage.ps1
|
function New-CIEMScanHistoryPage { <# .SYNOPSIS Creates the Scan History page with a data grid of past scan runs. .PARAMETER Navigation Array of UDListItem components for sidebar navigation. #> [CmdletBinding()] param( [Parameter(Mandatory)] [object[]]$Navigation ) New-UDPage -Name 'Scan History' -Url '/ciem/history' -Content { New-UDTypography -Text 'Scan History' -Variant 'h4' -Style @{ marginBottom = '10px'; marginTop = '10px' } New-UDTypography -Text 'Click on a scan to expand and view detailed results' -Variant 'subtitle1' -Style @{ marginBottom = '20px'; color = '#666' } New-UDCard -Content { New-UDDynamic -Id 'scanHistoryPanel' -Content { try { $scanRuns = @(Get-CIEMScanRun) if ($scanRuns -and $scanRuns.Count -gt 0) { New-UDDataGrid -LoadRows { $runs = @(Get-CIEMScanRun) $historyData = $runs | ForEach-Object { @{ id = $_.Id date = ([datetime]$_.StartTime).ToString('yyyy-MM-dd HH:mm') providers = ($_.Providers -join ', ') services = ($_.Services -join ', ') status = [string]$_.Status failed = $_.FailedResults passed = $_.PassedResults manual = $_.ManualResults skipped = $_.SkippedResults duration = $_.Duration } } @($historyData) | Out-UDDataGridData -Context $EventData -TotalRows @($historyData).Count } -Columns @( New-UDDataGridColumn -Field 'date' -HeaderName 'Scan Date' -Width 150 New-UDDataGridColumn -Field 'providers' -HeaderName 'Providers' -Width 150 New-UDDataGridColumn -Field 'services' -HeaderName 'Services' -Flex 1 New-UDDataGridColumn -Field 'status' -HeaderName 'Status' -Width 110 -Render { $statusColors = @{ 'Completed' = '#4caf50'; 'Running' = '#2196f3'; 'Failed' = '#f44336' } $color = $statusColors[$EventData.status] if (-not $color) { $color = '#666' } New-UDChip -Label $EventData.status -Size 'small' -Style @{ backgroundColor = $color; color = 'white' } } New-UDDataGridColumn -Field 'failed' -HeaderName 'Failed' -Width 90 -Render { $color = if ($EventData.failed -gt 0) { '#f44336' } else { '#4caf50' } New-UDChip -Label $EventData.failed -Size 'small' -Style @{ backgroundColor = $color; color = 'white' } } New-UDDataGridColumn -Field 'passed' -HeaderName 'Passed' -Width 90 -Render { New-UDChip -Label $EventData.passed -Size 'small' -Style @{ backgroundColor = '#4caf50'; color = 'white' } } New-UDDataGridColumn -Field 'manual' -HeaderName 'Manual' -Width 90 -Render { $color = if ($EventData.manual -gt 0) { '#1565c0' } else { '#9e9e9e' } New-UDChip -Label $EventData.manual -Size 'small' -Style @{ backgroundColor = $color; color = 'white' } } New-UDDataGridColumn -Field 'skipped' -HeaderName 'Skipped' -Width 90 -Render { $color = if ($EventData.skipped -gt 0) { '#ff9800' } else { '#9e9e9e' } New-UDChip -Label $EventData.skipped -Size 'small' -Style @{ backgroundColor = $color; color = 'white' } } New-UDDataGridColumn -Field 'duration' -HeaderName 'Duration' -Width 100 ) -AutoHeight $true -Pagination -PageSize 10 -ExportOptions @('CSV', 'JSON') -OnExport { $runs = @(Get-CIEMScanRun -IncludeResults) if ($EventData.Type -eq 'CSV') { $csvData = $runs | ForEach-Object { [PSCustomObject]@{ Id = $_.Id Providers = ($_.Providers -join ', ') Services = ($_.Services -join ', ') Status = [string]$_.Status StartTime = ([datetime]$_.StartTime).ToString('yyyy-MM-dd HH:mm:ss') EndTime = if ($_.EndTime) { ([datetime]$_.EndTime).ToString('yyyy-MM-dd HH:mm:ss') } else { '' } Duration = $_.Duration Failed = $_.FailedResults Passed = $_.PassedResults Manual = $_.ManualResults Skipped = $_.SkippedResults } } $exportContent = $csvData | ConvertTo-Csv -NoTypeInformation | Out-String Out-UDDataGridExport -Data $exportContent -FileName "ciem-scans-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv" } elseif ($EventData.Type -eq 'JSON') { $jsonData = $runs | ForEach-Object { [ordered]@{ id = $_.Id providers = @($_.Providers) services = $_.Services status = [string]$_.Status startTime = ([datetime]$_.StartTime).ToString('o') endTime = if ($_.EndTime) { ([datetime]$_.EndTime).ToString('o') } else { $null } duration = $_.Duration failedCount = $_.FailedResults passedCount = $_.PassedResults manualCount = $_.ManualResults skippedCount = $_.SkippedResults scan_results = @($_.ScanResults | ForEach-Object { [ordered]@{ checkId = $_.Check.Id status = $_.Status severity = [string]$_.Check.Severity resourceId = $_.ResourceId resourceName = $_.ResourceName location = $_.Location statusExtended = $_.StatusExtended } }) } } $exportContent = $jsonData | ConvertTo-Json -Depth 10 Out-UDDataGridExport -Data $exportContent -FileName "ciem-scans-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" } } -LoadDetailContent { # Load scan results for this specific scan run $scanRunId = $EventData.row.id try { $scanRun = Get-CIEMScanRun -Id $scanRunId -IncludeResults $rawResults = $scanRun.ScanResults if ($rawResults -and $rawResults.Count -gt 0) { $enrichedResults = $rawResults | ForEach-Object { $result = $_ @{ id = $result.Check.Id + '_' + ($result.ResourceId -replace '[^\w]', '_') checkId = $result.Check.Id title = $result.Check.Title severity = ([string]$result.Check.Severity -replace '^(.)', { $_.Groups[1].Value.ToUpper() }) status = $result.Status service = [string]$result.Check.Service resourceId = $result.ResourceId resourceName = $result.ResourceName location = $result.Location description = $result.Check.Description statusExtended = $result.StatusExtended provider = if ($result.Check.Provider) { [string]$result.Check.Provider } else { 'Azure' } remediation = if ($result.Check.Remediation -and $result.Check.Remediation.Text) { $result.Check.Remediation.Text } else { 'See Devolutions PAM for remediation guidance.' } relatedUrl = $result.Check.RelatedUrl } } New-UDElement -Tag 'div' -Attributes @{ style = @{ padding = '16px'; backgroundColor = '#fafafa' } } -Content { # Summary chips $failedCount = @($enrichedResults | Where-Object { $_.status -eq 'FAIL' }).Count $passedCount = @($enrichedResults | Where-Object { $_.status -eq 'PASS' }).Count $manualCount = @($enrichedResults | Where-Object { $_.status -eq 'MANUAL' }).Count $skippedCount = @($enrichedResults | Where-Object { $_.status -eq 'SKIPPED' }).Count New-UDElement -Tag 'div' -Attributes @{ style = @{ marginBottom = '16px' } } -Content { New-UDStack -Direction 'row' -Spacing 2 -AlignItems 'center' -Content { New-UDTypography -Text 'Scan Results' -Variant 'h6' New-UDChip -Label "Failed: $failedCount" -Size 'small' -Style @{ backgroundColor = '#ffebee'; color = '#c62828' } New-UDChip -Label "Passed: $passedCount" -Size 'small' -Style @{ backgroundColor = '#e8f5e9'; color = '#2e7d32' } if ($manualCount -gt 0) { New-UDChip -Label "Manual: $manualCount" -Size 'small' -Style @{ backgroundColor = '#e3f2fd'; color = '#1565c0' } } if ($skippedCount -gt 0) { New-UDChip -Label "Skipped: $skippedCount" -Size 'small' -Style @{ backgroundColor = '#fff3e0'; color = '#e65100' } } } } # Results DataGrid $resultsData = $enrichedResults New-UDDataGrid -LoadRows { $resultsData | Out-UDDataGridData -Context $EventData -TotalRows @($resultsData).Count } -Columns @( New-UDDataGridColumn -Field 'checkId' -HeaderName 'Check ID' -Width 200 New-UDDataGridColumn -Field 'title' -HeaderName 'Finding' -Flex 1 New-UDDataGridColumn -Field 'severity' -HeaderName 'Severity' -Width 110 -Render { $sev = $EventData.severity.ToUpper() $color = Get-SeverityColor -Severity $sev New-UDChip -Label $sev -Style @{ backgroundColor = $color; color = 'white' } } New-UDDataGridColumn -Field 'status' -HeaderName 'Status' -Width 100 -Render { $statusColors = @{ 'FAIL' = '#f44336'; 'PASS' = '#4caf50'; 'MANUAL' = '#ff9800'; 'SKIPPED' = '#9e9e9e' } $color = $statusColors[$EventData.status] if (-not $color) { $color = '#666' } New-UDChip -Label $EventData.status -Style @{ backgroundColor = $color; color = 'white' } } New-UDDataGridColumn -Field 'statusExtended' -HeaderName 'Status Description' -Flex 1 New-UDDataGridColumn -Field 'service' -HeaderName 'Service' -Width 100 New-UDDataGridColumn -Field 'provider' -HeaderName 'Provider' -Width 100 New-UDDataGridColumn -Field 'resourceName' -HeaderName 'Resource' -Width 180 ) -AutoHeight $true -Pagination -PageSize 25 -ShowQuickFilter } } else { New-UDElement -Tag 'div' -Attributes @{ style = @{ padding = '20px'; textAlign = 'center' } } -Content { New-UDTypography -Text 'No results available for this scan.' -Variant 'body2' -Style @{ color = '#666' } } } } catch { New-UDElement -Tag 'div' -Attributes @{ style = @{ padding = '20px'; textAlign = 'center' } } -Content { New-UDTypography -Text "Error loading results: $($_.Exception.Message)" -Variant 'body2' -Style @{ color = '#f44336' } } } } } else { New-UDTypography -Text 'No scan history available yet. Run a scan from the Scan page to see results here.' -Variant 'body2' -Style @{ color = '#666'; fontStyle = 'italic'; padding = '16px' } } } catch { New-UDTypography -Text 'Unable to load scan history.' -Variant 'body2' -Style @{ color = '#666'; fontStyle = 'italic'; padding = '16px' } } } } } -Navigation $Navigation -NavigationLayout permanent } |