Private/New-HtmlDashboard.ps1

function New-HtmlDashboard {
    <#
    .SYNOPSIS
        Generates an HTML dashboard report with inline CSS.
    .DESCRIPTION
        Creates styled HTML reports for execution results, health scores, and shift handoff.
        Uses inline CSS only (no external stylesheets) with the accent color #f97316 (orange-fire).
        Supports multiple report types: ExecutionReport, HealthScore, ShiftHandoff.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('ExecutionReport', 'HealthScore', 'ShiftHandoff')]
        [string]$ReportType,

        [Parameter(Mandatory)]
        [object]$Data,

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

        [Parameter()]
        [string]$Title
    )

    $accentColor = '#f97316'
    $accentDark = '#ea580c'
    $bgColor = '#0f172a'
    $cardBg = '#1e293b'
    $textColor = '#e2e8f0'
    $textMuted = '#94a3b8'
    $successColor = '#22c55e'
    $warningColor = '#eab308'
    $errorColor = '#ef4444'
    $infoColor = '#3b82f6'

    $css = @"
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: $bgColor; color: $textColor; padding: 2rem; line-height: 1.6; }
        .container { max-width: 1200px; margin: 0 auto; }
        .header { background: linear-gradient(135deg, $accentColor, $accentDark); padding: 2rem; border-radius: 12px; margin-bottom: 2rem; }
        .header h1 { font-size: 1.8rem; font-weight: 700; color: white; }
        .header p { color: rgba(255,255,255,0.85); margin-top: 0.5rem; }
        .card { background: $cardBg; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid rgba(255,255,255,0.05); }
        .card h2 { color: $accentColor; font-size: 1.2rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid rgba(255,255,255,0.1); }
        .card h3 { color: $textColor; font-size: 1rem; margin-bottom: 0.75rem; }
        .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; margin-bottom: 1.5rem; }
        .stat-card { background: $cardBg; border-radius: 12px; padding: 1.5rem; border: 1px solid rgba(255,255,255,0.05); text-align: center; }
        .stat-value { font-size: 2.5rem; font-weight: 700; color: $accentColor; }
        .stat-label { color: $textMuted; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
        table { width: 100%; border-collapse: collapse; margin-top: 0.5rem; }
        th { background: rgba(249,115,22,0.15); color: $accentColor; padding: 0.75rem 1rem; text-align: left; font-weight: 600; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.03em; }
        td { padding: 0.75rem 1rem; border-bottom: 1px solid rgba(255,255,255,0.05); font-size: 0.9rem; }
        tr:hover td { background: rgba(255,255,255,0.02); }
        .badge { display: inline-block; padding: 0.2rem 0.6rem; border-radius: 999px; font-size: 0.75rem; font-weight: 600; }
        .badge-success { background: rgba(34,197,94,0.15); color: $successColor; }
        .badge-warning { background: rgba(234,179,8,0.15); color: $warningColor; }
        .badge-error { background: rgba(239,68,68,0.15); color: $errorColor; }
        .badge-info { background: rgba(59,130,246,0.15); color: $infoColor; }
        .badge-neutral { background: rgba(148,163,184,0.15); color: $textMuted; }
        .score-ring { width: 120px; height: 120px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 1rem auto; font-size: 2rem; font-weight: 700; }
        .grade-a { border: 4px solid $successColor; color: $successColor; }
        .grade-b { border: 4px solid #22d3ee; color: #22d3ee; }
        .grade-c { border: 4px solid $warningColor; color: $warningColor; }
        .grade-d { border: 4px solid $accentColor; color: $accentColor; }
        .grade-f { border: 4px solid $errorColor; color: $errorColor; }
        .step-flow { position: relative; padding-left: 2rem; }
        .step-item { position: relative; padding-bottom: 1.5rem; padding-left: 1.5rem; border-left: 2px solid rgba(255,255,255,0.1); }
        .step-item:last-child { border-left: 2px solid transparent; }
        .step-dot { position: absolute; left: -0.5rem; top: 0.2rem; width: 1rem; height: 1rem; border-radius: 50%; }
        .step-dot.success { background: $successColor; }
        .step-dot.failure { background: $errorColor; }
        .step-dot.skipped { background: $textMuted; }
        .step-dot.pending { background: $warningColor; }
        .step-title { font-weight: 600; margin-bottom: 0.25rem; }
        .step-detail { color: $textMuted; font-size: 0.85rem; }
        .footer { text-align: center; color: $textMuted; font-size: 0.8rem; margin-top: 2rem; padding-top: 1rem; border-top: 1px solid rgba(255,255,255,0.05); }
        pre { background: rgba(0,0,0,0.3); padding: 1rem; border-radius: 8px; overflow-x: auto; font-size: 0.85rem; color: $textMuted; margin-top: 0.5rem; }
"@


    $timestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
    $bodyContent = ''

    switch ($ReportType) {
        'ExecutionReport' {
            if (-not $Title) { $Title = "Runbook Execution Report" }

            $statusBadge = switch ($Data.Status) {
                'Completed' { '<span class="badge badge-success">Completed</span>' }
                'Failed'    { '<span class="badge badge-error">Failed</span>' }
                'Escalated' { '<span class="badge badge-warning">Escalated</span>' }
                'Aborted'   { '<span class="badge badge-error">Aborted</span>' }
                default     { '<span class="badge badge-neutral">' + $Data.Status + '</span>' }
            }

            $duration = if ($Data.Duration) { $Data.Duration } else { 'N/A' }

            $bodyContent = @"
<div class="header">
    <h1>$Title</h1>
    <p>Runbook: $($Data.RunbookName) | Target: $($Data.ComputerName) | $timestamp</p>
</div>
 
<div class="grid">
    <div class="stat-card">
        <div class="stat-value">$statusBadge</div>
        <div class="stat-label">Status</div>
    </div>
    <div class="stat-card">
        <div class="stat-value">$duration</div>
        <div class="stat-label">Duration</div>
    </div>
    <div class="stat-card">
        <div class="stat-value">$(if ($Data.StepResults) { @($Data.StepResults).Count } else { 0 })</div>
        <div class="stat-label">Steps Executed</div>
    </div>
</div>
"@


            # Step flow
            if ($Data.StepResults) {
                $bodyContent += '<div class="card"><h2>Execution Flow</h2><div class="step-flow">'
                foreach ($step in $Data.StepResults) {
                    $dotClass = switch ($step.Status) {
                        'Success'  { 'success' }
                        'Failed'   { 'failure' }
                        'Skipped'  { 'skipped' }
                        default    { 'pending' }
                    }
                    $stepBadge = switch ($step.Status) {
                        'Success'  { '<span class="badge badge-success">Success</span>' }
                        'Failed'   { '<span class="badge badge-error">Failed</span>' }
                        'Skipped'  { '<span class="badge badge-neutral">Skipped</span>' }
                        default    { '<span class="badge badge-info">' + $step.Status + '</span>' }
                    }

                    $stepOutput = ''
                    if ($step.Output) {
                        $escapedOutput = [System.Web.HttpUtility]::HtmlEncode(($step.Output | Out-String).Trim())
                        if ($escapedOutput.Length -gt 0) {
                            $stepOutput = "<pre>$escapedOutput</pre>"
                        }
                    }

                    $bodyContent += @"
<div class="step-item">
    <div class="step-dot $dotClass"></div>
    <div class="step-title">$($step.StepId) - $($step.Description) $stepBadge</div>
    <div class="step-detail">Action: $($step.Action) | Duration: $($step.Duration)</div>
    $stepOutput
</div>
"@

                }
                $bodyContent += '</div></div>'
            }

            # Approval log
            if ($Data.ApprovalLog -and @($Data.ApprovalLog).Count -gt 0) {
                $bodyContent += '<div class="card"><h2>Approval Log</h2><table><tr><th>Step</th><th>Method</th><th>Approved</th><th>Approver</th><th>Time</th></tr>'
                foreach ($approval in $Data.ApprovalLog) {
                    $approvedBadge = if ($approval.Approved) { '<span class="badge badge-success">Yes</span>' } else { '<span class="badge badge-error">No</span>' }
                    $bodyContent += "<tr><td>$($approval.StepDescription)</td><td>$($approval.Method)</td><td>$approvedBadge</td><td>$($approval.Approver)</td><td>$($approval.ResponseTime)s</td></tr>"
                }
                $bodyContent += '</table></div>'
            }

            # Verification results
            if ($Data.VerificationResults -and @($Data.VerificationResults).Count -gt 0) {
                $bodyContent += '<div class="card"><h2>Verification Results</h2><table><tr><th>Step</th><th>Verified</th><th>Attempts</th><th>Result</th></tr>'
                foreach ($verify in $Data.VerificationResults) {
                    $verifiedBadge = if ($verify.Verified) { '<span class="badge badge-success">Verified</span>' } else { '<span class="badge badge-error">Not Verified</span>' }
                    $bodyContent += "<tr><td>$($verify.StepId)</td><td>$verifiedBadge</td><td>$($verify.Attempts)</td><td>$($verify.FinalResult)</td></tr>"
                }
                $bodyContent += '</table></div>'
            }
        }

        'HealthScore' {
            if (-not $Title) { $Title = "CI Health Score Report" }

            $gradeClass = switch ($Data.Grade) {
                'A' { 'grade-a' }
                'B' { 'grade-b' }
                'C' { 'grade-c' }
                'D' { 'grade-d' }
                'F' { 'grade-f' }
                default { 'grade-c' }
            }

            $trendIcon = switch ($Data.Trend) {
                'Improving' { '&#x2191;' }
                'Declining' { '&#x2193;' }
                'Stable'    { '&#x2194;' }
                default     { '&mdash;' }
            }

            $bodyContent = @"
<div class="header">
    <h1>$Title</h1>
    <p>$($Data.ComputerName) | Generated: $timestamp</p>
</div>
 
<div class="grid">
    <div class="stat-card">
        <div class="score-ring $gradeClass">$($Data.Grade)</div>
        <div class="stat-label">Health Grade</div>
    </div>
    <div class="stat-card">
        <div class="stat-value">$($Data.Score)</div>
        <div class="stat-label">Health Score</div>
    </div>
    <div class="stat-card">
        <div class="stat-value">$trendIcon</div>
        <div class="stat-label">Trend: $($Data.Trend)</div>
    </div>
</div>
"@


            # Factors
            if ($Data.Factors -and @($Data.Factors).Count -gt 0) {
                $bodyContent += '<div class="card"><h2>Health Factors</h2><table><tr><th>Factor</th><th>Impact</th><th>Details</th></tr>'
                foreach ($factor in $Data.Factors) {
                    $impactBadge = if ($factor.Impact -lt 0) {
                        '<span class="badge badge-error">' + $factor.Impact + '</span>'
                    } else {
                        '<span class="badge badge-success">+' + $factor.Impact + '</span>'
                    }
                    $bodyContent += "<tr><td>$($factor.Name)</td><td>$impactBadge</td><td>$($factor.Details)</td></tr>"
                }
                $bodyContent += '</table></div>'
            }

            # Recommendations
            if ($Data.Recommendations -and @($Data.Recommendations).Count -gt 0) {
                $bodyContent += '<div class="card"><h2>Recommendations</h2>'
                foreach ($rec in $Data.Recommendations) {
                    $bodyContent += "<p style=`"margin-bottom: 0.5rem; padding-left: 1rem; border-left: 3px solid $accentColor;`">$rec</p>"
                }
                $bodyContent += '</div>'
            }
        }

        'ShiftHandoff' {
            if (-not $Title) { $Title = "Shift Handoff Report" }

            $bodyContent = @"
<div class="header">
    <h1>$Title</h1>
    <p>Period: Last $($Data.HoursBack) hours | Generated: $timestamp</p>
</div>
 
<div class="grid">
    <div class="stat-card">
        <div class="stat-value">$(if ($Data.RunbookActivity) { @($Data.RunbookActivity).Count } else { 0 })</div>
        <div class="stat-label">Runbook Executions</div>
    </div>
    <div class="stat-card">
        <div class="stat-value">$(if ($Data.Escalations) { @($Data.Escalations).Count } else { 0 })</div>
        <div class="stat-label">Escalations</div>
    </div>
    <div class="stat-card">
        <div class="stat-value">$(if ($Data.PendingApprovals) { @($Data.PendingApprovals).Count } else { 0 })</div>
        <div class="stat-label">Pending Approvals</div>
    </div>
</div>
"@


            # Runbook Activity
            if ($Data.RunbookActivity -and @($Data.RunbookActivity).Count -gt 0) {
                $bodyContent += '<div class="card"><h2>Runbook Activity</h2><table><tr><th>Runbook</th><th>Target</th><th>Status</th><th>Time</th></tr>'
                foreach ($activity in $Data.RunbookActivity) {
                    $sBadge = switch ($activity.Status) {
                        'Completed' { '<span class="badge badge-success">Completed</span>' }
                        'Failed'    { '<span class="badge badge-error">Failed</span>' }
                        'Escalated' { '<span class="badge badge-warning">Escalated</span>' }
                        default     { '<span class="badge badge-neutral">' + $activity.Status + '</span>' }
                    }
                    $bodyContent += "<tr><td>$($activity.RunbookName)</td><td>$($activity.ComputerName)</td><td>$sBadge</td><td>$($activity.StartTime)</td></tr>"
                }
                $bodyContent += '</table></div>'
            }

            # Escalations
            if ($Data.Escalations -and @($Data.Escalations).Count -gt 0) {
                $bodyContent += '<div class="card"><h2>Escalations</h2><table><tr><th>Runbook</th><th>Target</th><th>Reason</th><th>Priority</th></tr>'
                foreach ($esc in $Data.Escalations) {
                    $bodyContent += "<tr><td>$($esc.RunbookName)</td><td>$($esc.ComputerName)</td><td>$($esc.Message)</td><td>$($esc.Priority)</td></tr>"
                }
                $bodyContent += '</table></div>'
            }

            # Pending Approvals
            if ($Data.PendingApprovals -and @($Data.PendingApprovals).Count -gt 0) {
                $bodyContent += '<div class="card"><h2>Pending Approvals</h2><table><tr><th>Runbook</th><th>Step</th><th>Target</th><th>Requested At</th></tr>'
                foreach ($pending in $Data.PendingApprovals) {
                    $bodyContent += "<tr><td>$($pending.RunbookName)</td><td>$($pending.StepDescription)</td><td>$($pending.ComputerName)</td><td>$($pending.RequestedAt)</td></tr>"
                }
                $bodyContent += '</table></div>'
            }

            # Next Shift Notes
            if ($Data.NextShiftNotes -and @($Data.NextShiftNotes).Count -gt 0) {
                $bodyContent += '<div class="card"><h2>Notes for Next Shift</h2>'
                foreach ($note in $Data.NextShiftNotes) {
                    $bodyContent += "<p style=`"margin-bottom: 0.5rem; padding-left: 1rem; border-left: 3px solid $accentColor;`">$note</p>"
                }
                $bodyContent += '</div>'
            }
        }
    }

    $html = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>$Title</title>
    <style>
$css
    </style>
</head>
<body>
    <div class="container">
        $bodyContent
        <div class="footer">
            Generated by Infra-RunbookEngine v1.0.0 | $timestamp
        </div>
    </div>
</body>
</html>
"@


    # Ensure output directory exists
    $outDir = Split-Path $OutputPath -Parent
    if ($outDir -and -not (Test-Path $outDir)) {
        New-Item -Path $outDir -ItemType Directory -Force | Out-Null
    }

    $html | Set-Content -Path $OutputPath -Encoding UTF8

    return $OutputPath
}