Private/New-HtmlDashboard.ps1

function New-HtmlDashboard {
    <#
    .SYNOPSIS
        Generates an HTML dashboard report from audit sections.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [array]$Sections,

        [Parameter(Mandatory)]
        [string]$OutputFile,

        [Parameter()]
        [string]$ReportTitle = 'Entra ID Security Audit'
    )

    $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    $hostname  = $env:COMPUTERNAME

    $sectionHtml = foreach ($section in $Sections) {
        $rows = foreach ($item in $section.Data) {
            $cells = foreach ($prop in $item.PSObject.Properties) {
                $value = $prop.Value
                $class = if ($prop.Name -eq 'Finding' -and $value -ne 'OK' -and $value -ne 'PIM Eligible (Good)') {
                    ' class="finding-bad"'
                }
                elseif ($prop.Name -eq 'Finding' -and ($value -eq 'OK' -or $value -eq 'PIM Eligible (Good)')) {
                    ' class="finding-ok"'
                }
                else { '' }
                " <td$class>$([System.Web.HttpUtility]::HtmlEncode($value))</td>"
            }
            " <tr>`n$($cells -join "`n")`n </tr>"
        }

        $headers = if ($section.Data.Count -gt 0) {
            $section.Data[0].PSObject.Properties.Name | ForEach-Object {
                " <th>$_</th>"
            }
        }

        @"
        <div class="section">
            <h2>$($section.Title)</h2>
            <p class="summary">$($section.Summary)</p>
            <div class="table-wrapper">
                <table>
                    <thead>
                        <tr>
$($headers -join "`n")
                        </tr>
                    </thead>
                    <tbody>
$($rows -join "`n")
                    </tbody>
                </table>
            </div>
        </div>
"@

    }

    $totalFindings = ($Sections | ForEach-Object {
        $_.Data | Where-Object { $_.Finding -and $_.Finding -ne 'OK' -and $_.Finding -ne 'PIM Eligible (Good)' }
    } | Measure-Object).Count

    $html = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>$ReportTitle</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', Tahoma, sans-serif; background: #0d1117; color: #c9d1d9; padding: 2rem; }
        .header { background: linear-gradient(135deg, #1a1f2e 0%, #0f3460 100%); padding: 2rem; border-radius: 8px; margin-bottom: 2rem; }
        .header h1 { color: #58a6ff; font-size: 1.8rem; margin-bottom: 0.5rem; }
        .header .meta { color: #8b949e; font-size: 0.9rem; }
        .summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
        .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 1.5rem; text-align: center; }
        .card .number { font-size: 2.5rem; font-weight: 700; }
        .card .label { color: #8b949e; font-size: 0.85rem; margin-top: 0.5rem; }
        .card.findings .number { color: #f85149; }
        .card.clean .number { color: #3fb950; }
        .section { background: #161b22; border: 1px solid #30363d; border-radius: 8px; margin-bottom: 1.5rem; padding: 1.5rem; }
        .section h2 { color: #58a6ff; font-size: 1.3rem; margin-bottom: 0.5rem; }
        .section .summary { color: #8b949e; margin-bottom: 1rem; font-size: 0.9rem; }
        .table-wrapper { overflow-x: auto; }
        table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
        th { background: #21262d; color: #58a6ff; padding: 0.75rem; text-align: left; border-bottom: 2px solid #30363d; white-space: nowrap; }
        td { padding: 0.6rem 0.75rem; border-bottom: 1px solid #21262d; }
        tr:hover { background: #1c2128; }
        .finding-bad { color: #f85149; font-weight: 600; }
        .finding-ok { color: #3fb950; }
        .footer { text-align: center; color: #484f58; margin-top: 2rem; font-size: 0.8rem; }
    </style>
</head>
<body>
    <div class="header">
        <h1>$ReportTitle</h1>
        <div class="meta">Generated $timestamp | Host: $hostname | EntraID-SecurityAudit v1.0.0</div>
    </div>
 
    <div class="summary-cards">
        <div class="card">
            <div class="number">$($Sections.Count)</div>
            <div class="label">Audit Sections</div>
        </div>
        <div class="card$(if ($totalFindings -gt 0) { ' findings' } else { ' clean' })">
            <div class="number">$totalFindings</div>
            <div class="label">Total Findings</div>
        </div>
    </div>
 
$($sectionHtml -join "`n")
 
    <div class="footer">
        Generated by EntraID-SecurityAudit | github.com/larro1991/EntraID-SecurityAudit
    </div>
</body>
</html>
"@


    $html | Out-File -FilePath $OutputFile -Encoding UTF8 -Force
}