Reports/HtmlReportGenerator.ps1
|
# Note: Models module is already imported by the main module # ConvertTo-HtmlEncoded and other functions are available from the parent scope function New-HtmlReport { <# .SYNOPSIS Generates an HTML report from report data using modern template. .DESCRIPTION Creates a comprehensive HTML report by loading the modern template and populating with data. .PARAMETER ReportData Report data hashtable containing all control information. .PARAMETER TemplatePath Path to the HTML template directory. .PARAMETER OutputPath Path where the HTML report will be saved. .PARAMETER InlineAssets If specified, CSS and JS will be inlined into the HTML file. .OUTPUTS Path to the generated HTML file. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [hashtable]$ReportData, [Parameter(Mandatory = $true)] [string]$TemplatePath, [Parameter(Mandatory = $true)] [string]$OutputPath, [Parameter(Mandatory = $false)] [switch]$InlineAssets ) try { # Use the modern template $templateFile = Join-Path $TemplatePath "report-modern-template.html" if (-not (Test-Path $templateFile)) { throw "Modern template file not found: $templateFile" } # Load template content $htmlContent = Get-Content -Path $templateFile -Raw -Encoding UTF8 # Build control cards HTML $controlCards = Build-ControlCards -ReportData $ReportData Write-Verbose "Generated $($ReportData.ProposedChanges.Count) control cards, HTML length: $($controlCards.Length) characters" # Calculate summary statistics $summary = $ReportData.ExecutiveSummary $totalChecks = $summary.TotalChecks $compliant = $summary.Compliant $nonCompliant = $summary.NonCompliant $notApplicable = $summary.NotApplicable $currentScore = $summary.CurrentScore $maxScore = $summary.MaxScore $compliancePercentage = if ($totalChecks -gt 0) { [math]::Round(($compliant / $totalChecks) * 100, 1) } else { 0 } $scorePercentage = if ($maxScore -gt 0) { [math]::Round(($currentScore / $maxScore) * 100, 1) } else { 0 } # Calculate stroke-dashoffset for circular progress (628 is circumference of circle with r=100) $strokeDashoffset = [math]::Round(628 * (1 - ($scorePercentage / 100)), 0) # HTML encode all dynamic values $encodedTenantName = ConvertTo-HtmlEncoded -Text $ReportData.Metadata.TenantName $encodedTenantId = ConvertTo-HtmlEncoded -Text $ReportData.Metadata.TenantId $encodedRunByUser = ConvertTo-HtmlEncoded -Text $ReportData.Metadata.GeneratedBy $encodedReportDate = ConvertTo-HtmlEncoded -Text $ReportData.Metadata.GeneratedDate # Replace all template placeholders (using simple string replacement for reliability) $htmlContent = $htmlContent.Replace('{{TENANT_NAME}}', $encodedTenantName) $htmlContent = $htmlContent.Replace('{{TENANT_ID}}', $encodedTenantId) $htmlContent = $htmlContent.Replace('{{RUN_BY_USER}}', $encodedRunByUser) $htmlContent = $htmlContent.Replace('{{REPORT_DATE}}', $encodedReportDate) $htmlContent = $htmlContent.Replace('{{TOTAL_CHECKS}}', $totalChecks) $htmlContent = $htmlContent.Replace('{{COMPLIANT}}', $compliant) $htmlContent = $htmlContent.Replace('{{NON_COMPLIANT}}', $nonCompliant) $htmlContent = $htmlContent.Replace('{{NOT_APPLICABLE}}', $notApplicable) $htmlContent = $htmlContent.Replace('{{CURRENT_SCORE}}', $currentScore) $htmlContent = $htmlContent.Replace('{{MAX_SCORE}}', $maxScore) $htmlContent = $htmlContent.Replace('{{SCORE_PERCENTAGE}}', $scorePercentage) $htmlContent = $htmlContent.Replace('{{COMPLIANCE_PERCENTAGE}}', $compliancePercentage) $htmlContent = $htmlContent.Replace('{{STROKE_DASHOFFSET}}', $strokeDashoffset) $htmlContent = $htmlContent.Replace('{{CONTROL_CARDS}}', $controlCards) Write-Verbose "Placeholder replacements completed" # Write final HTML to file $htmlContent | Set-Content -Path $OutputPath -Encoding UTF8 -Force return $OutputPath } catch { throw "Failed to generate HTML report: $_" } } function Build-ControlCards { <# .SYNOPSIS Builds the HTML for individual control cards. .DESCRIPTION Generates modern control card HTML for all controls (no category grouping). .PARAMETER ReportData Report data containing all controls. .OUTPUTS HTML string containing all control cards. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [hashtable]$ReportData ) $controlCardsHtml = "" # Sort controls by risk (High first), then by compliance status $sortedControls = $ReportData.ProposedChanges | Sort-Object @{ Expression = { switch ($_.Risk) { 'High' { 1 } 'Medium' { 2 } 'Low' { 3 } } } }, @{ Expression = { switch ($_.Status) { 'NonCompliant' { 1 } 'Compliant' { 2 } 'NotApplicable' { 3 } } } } foreach ($item in $sortedControls) { $controlCardsHtml += Build-ModernControlCard -Item $item } return $controlCardsHtml } function Build-ModernControlCard { <# .SYNOPSIS Builds HTML for a single modern control card. .DESCRIPTION Generates the HTML markup for a control card in the new modern design. .PARAMETER Item Control item data. .OUTPUTS HTML string for the control card. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [hashtable]$Item ) # Map status to CSS classes $statusClass = switch ($Item.Status) { "Compliant" { "status-compliant" } "NonCompliant" { "status-noncompliant" } "NotApplicable" { "status-na" } default { "status-na" } } $statusDataValue = switch ($Item.Status) { "Compliant" { "compliant" } "NonCompliant" { "noncompliant" } "NotApplicable" { "na" } default { "na" } } $riskDataValue = $Item.Risk.ToLower() # Map status to badge classes $statusBadgeClass = switch ($Item.Status) { "Compliant" { "badge-status-compliant" } "NonCompliant" { "badge-status-noncompliant" } "NotApplicable" { "badge-status-na" } default { "badge-status-na" } } $riskBadgeClass = switch ($Item.Risk) { "High" { "badge-risk-high" } "Medium" { "badge-risk-medium" } "Low" { "badge-risk-low" } } $statusText = switch ($Item.Status) { "Compliant" { "Compliant" } "NonCompliant" { "Non-Compliant" } "NotApplicable" { "N/A" } default { "Unknown" } } # HTML encode all dynamic values $encodedSettingName = ConvertTo-HtmlEncoded -Text $Item.SettingName $encodedCategory = ConvertTo-HtmlEncoded -Text $Item.Category $encodedJustification = ConvertTo-HtmlEncoded -Text $Item.Justification $encodedCurrentValue = ConvertTo-HtmlEncoded -Text $Item.CurrentValue $encodedProposedValue = ConvertTo-HtmlEncoded -Text $Item.ProposedValue $encodedImpact = ConvertTo-HtmlEncoded -Text $Item.SecureScoreImpact $encodedRisk = ConvertTo-HtmlEncoded -Text $Item.Risk $encodedActionUrl = ConvertTo-HtmlEncoded -Text $Item.ActionUrl $encodedReference = ConvertTo-HtmlEncoded -Text $Item.Reference # Build action buttons $actionButtonsHtml = "" if ($Item.ActionUrl) { $actionButtonsHtml = @" <a href="$encodedActionUrl" target="_blank" class="action-btn action-btn-primary"> <span>⚙️</span> <span>Configure Setting</span> </a> <a href="$encodedReference" target="_blank" class="action-btn action-btn-secondary"> <span>📚</span> <span>View Documentation</span> </a> "@ } elseif ($Item.Reference) { $actionButtonsHtml = @" <a href="$encodedReference" target="_blank" class="action-btn action-btn-secondary"> <span>📚</span> <span>View Documentation</span> </a> "@ } return @" <div class="control-card $statusClass fade-in" data-status="$statusDataValue" data-risk="$riskDataValue"> <div class="control-header" onclick="toggleControl(this)"> <div class="control-info"> <div class="control-title">$encodedSettingName</div> <div class="control-meta"> <span class="control-category">$encodedCategory</span> <div class="control-badges"> <span class="badge $statusBadgeClass">$statusText</span> <span class="badge $riskBadgeClass">$encodedRisk Risk</span> </div> </div> </div> <span class="impact-score">$encodedImpact</span> <div class="expand-toggle"> <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M4 6l4 4 4-4"/> </svg> </div> </div> <div class="control-details"> <div class="justification-box"> $encodedJustification </div> <div class="detail-grid"> <div class="detail-item"> <div class="detail-label">Current Status</div> <div class="detail-value">$encodedCurrentValue</div> </div> <div class="detail-item"> <div class="detail-label">Recommended Configuration</div> <div class="detail-value">$encodedProposedValue</div> </div> </div> <div class="action-buttons"> $actionButtonsHtml </div> </div> </div> "@ } Export-ModuleMember -Function @( 'New-HtmlReport', 'Build-ControlCards', 'Build-ModernControlCard' ) |