Private/Create-htmlReport.ps1
function Generate-K8sHTMLReport { param ( [string]$outputPath, [string]$version = "v0.0.1", [string]$SubscriptionId, [string]$ResourceGroup, [string]$ClusterName, [switch]$aks ) function ConvertToCollapsible { param( [string]$Id, # unique HTML ID [string]$defaultText, # default <summary> label (e.g., "Show Table") [string]$content # the HTML to show/hide ) @" <details id='$Id' style='margin:10px 0;'> <summary style='font-size:16px; cursor:pointer;'>$defaultText</summary> $content </details> <script> document.addEventListener('DOMContentLoaded', function() { const detElem = document.getElementById('$Id'); if (!detElem) return; const sum = detElem.querySelector('summary'); detElem.addEventListener('toggle', () => { // If open, change label to "Hide Table", else revert to $defaultText sum.textContent = detElem.open ? 'Hide Table' : '$defaultText'; }); }); </script> "@ } # Ensure output file is cleared before writing if (Test-Path $outputPath) { Remove-Item $outputPath -Force } # Capture console output while still displaying it Write-Host "`n[🌐 Cluster Summary]" -ForegroundColor Cyan Write-Host -NoNewline "`n🤖 Fetching Cluster Information..." -ForegroundColor Yellow $clusterSummaryRaw = Show-ClusterSummary -Html *>&1 # Captures output while displaying it write-Host "`r🤖 Cluster Information fetched. " -ForegroundColor Green # **Run AKS Best Practices Checks** if ($aks) { Write-Host -NoNewline "`n🤖Running AKS Best Practices Checklist..." -ForegroundColor Cyan $aksBestPractices = Invoke-AKSBestPractices -SubscriptionId $SubscriptionId -ResourceGroup $ResourceGroup -ClusterName $ClusterName -Html # $aksBestPractices = [PSCustomObject]$aksBestPractices Write-Host "`r🤖 AKS Information fetched. " -ForegroundColor Green # Extract key values $aksPass = $aksBestPractices.Passed $aksFail = $aksBestPractices.Failed $aksTotal = $aksBestPractices.Total $aksScore = $aksBestPractices.Score $aksRating = $aksBestPractices.Rating $aksReportData = $aksBestPractices.Data # Convert best practices table into a collapsible section $collapsibleAKSHtml = ConvertToCollapsible -Id "aksSummary" -defaultText "Show Best Practices Report" -content $aksReportData # **Hero Rating Section** $ratingColorClass = switch ($aksRating) { "A" { "normal" } "B" { "warning" } "C" { "warning" } "D" { "critical" } "F" { "critical" } default { "unknown" } } $heroRatingHtml = @" <h2>AKS Best Practices Summary</h2> <div class="hero-metrics"> <div class="metric-card normal">✅ Passed: <strong>$aksPass</strong></div> <div class="metric-card critical">❌ Failed: <strong>$aksFail</strong></div> <div class="metric-card default">📊 Total Checks: <strong>$aksTotal</strong></div> <div class="metric-card $ratingColorClass">🎯 Score: <strong>$aksScore%</strong></div> <div class="metric-card $ratingColorClass">⭐ Rating: <strong>$aksRating</strong></div> </div> "@ $aksHealthCheck = @" <!-- AKS summary Container --> <div class="container"> <h1 id="aks">AKS Best Practices Details</h1> $heroRatingHtml <h2 id="aksFindings">AKS Best Practices Results</h2> <div class="table-container"> $collapsibleAKSHtml </div> </div> "@ $aksMenuItem = @" <li><a href="#aks">AKS Best Practices</a></li> "@ } # Capture all nodes at once so we get a complete ASCII table: $nodeConditionsHtml = Show-NodeConditions -Html -PageSize 999 $collapsibleNodeSection = ConvertToCollapsible -Id "nodeConditions" -defaultText "Show Table" -content $nodeConditionsHtml $nodeResources = Show-NodeResourceUsage -PageSize 999 -Html $collapsibleNodeResources = ConvertToCollapsible -Id "nodeResources" -defaultText "Show Table" -content $nodeResources $emptyNsHtml = Show-EmptyNamespaces -PageSize 999 -Html $collapsibleEmptyNsHtmls = ConvertToCollapsible -Id "emptyNamespace" -defaultText "Show Table" -content $emptyNsHtml $dsIssuesHtml = Show-DaemonSetIssues -PageSize 999 -Html $collapsibleDsIssuesHtml = ConvertToCollapsible -Id "daemonSetIssues" -defaultText "Show Table" -content $dsIssuesHtml $podsRestartHtml = Show-PodsWithHighRestarts -Html -PageSize 999 $collapsiblePodsRestartHtml = ConvertToCollapsible -Id "podsRestart" -defaultText "Show Table" -content $podsRestartHtml $podLongRunningHtml = Show-LongRunningPods -Html -PageSize 999 $collapsiblePodLongRunningHtml = ConvertToCollapsible -Id "podLongRunning" -defaultText "Show Table" -content $podLongRunningHtml $podFailHtml = Show-FailedPods -Html -PageSize 999 $collapsiblePodFailHtml = ConvertToCollapsible -Id "podFail" -defaultText "Show Table" -content $podFailHtml $podpendHtml = Show-PendingPods -Html -PageSize 999 $collapsiblePodPendingHtml = ConvertToCollapsible -Id "podPending" -defaultText "Show Table" -content $podpendHtml $crashloopHtml = Show-CrashLoopBackOffPods -Html -PageSize 999 $collapsibleCrashloopHtml = ConvertToCollapsible -Id "crashloop" -defaultText "Show Table" -content $crashloopHtml $leftoverdebugHtml = Show-LeftoverDebugPods -Html -PageSize 999 $collapsibleLeftoverdebugHtml = ConvertToCollapsible -Id "leftoverDebug" -defaultText "Show Table" -content $leftoverdebugHtml $stuckJobHtml = Show-StuckJobs -Html -PageSize 999 $collapsibleStuckJobHtml = ConvertToCollapsible -Id "stuckJobs" -defaultText "Show Table" -content $stuckJobHtml $jobFailHtml = Show-FailedJobs -Html -PageSize 999 $collapsibleJobFailHtml = ConvertToCollapsible -Id "jobFail" -defaultText "Show Table" -content $jobFailHtml $servicesWithoutEndpointsHtml = Show-ServicesWithoutEndpoints -Html -PageSize 999 $collapsibleServicesWithoutEndpointsHtml = ConvertToCollapsible -Id "servicesWithoutEndpoints" -defaultText "Show Table" -content $servicesWithoutEndpointsHtml $unmountedpvHtml = Show-UnusedPVCs -Html -PageSize 999 $collapsibleUnmountedpvHtml = ConvertToCollapsible -Id "unmountedPV" -defaultText "Show Table" -content $unmountedpvHtml $rbacmisconfigHtml = Check-RBACMisconfigurations -Html -PageSize 999 $collapsibleRbacmisconfigHtml = ConvertToCollapsible -Id "rbacMisconfig" -defaultText "Show Table" -content $rbacmisconfigHtml $orphanedConfigMapsHtml = Check-OrphanedConfigMaps -Html -PageSize 999 $collapsibleOrphanedConfigMapsHtml = ConvertToCollapsible -Id "orphanedConfigMaps" -defaultText "Show Table" -content $orphanedConfigMapsHtml $orphanedSecretsHtml = Check-OrphanedSecrets -Html -PageSize 999 $collapsibleOrphanedSecretsHtml = ConvertToCollapsible -Id "orphanedSecrets" -defaultText "Show Table" -content $orphanedSecretsHtml $eventSummaryHtml = Show-KubeEvents -Html -PageSize 999 $collapsibleEventSummaryHtml = ConvertToCollapsible -Id "eventSummary" -defaultText "Show Table" -content $eventSummaryHtml # Convert output array to a single string $clusterSummaryText = $clusterSummaryRaw -join "`n" # Debugging: Print the exact raw captured output # Write-Host "🔍 Full Cluster Summary Output:" # Write-Host $clusterSummaryText # Function to extract numerical values correctly function Extract-Metric($label, $data) { if ($data -match "$label\s*:\s*([\d]+)") { return [int]$matches[1] } return "0" } # Extract Cluster Name and Kubernetes Version properly $clusterName = "Unknown" $k8sVersion = "Unknown" # Read line by line for better extraction for ($i = 0; $i -lt $clusterSummaryRaw.Count; $i++) { $line = [string]$clusterSummaryRaw[$i] -replace "`r", "" -replace "`n", "" # Cluster Name is 2 lines below "Cluster Name" if ($line -match "Cluster Name\s*$") { $clusterName = [string]$clusterSummaryRaw[$i + 2] -replace "`r", "" -replace "`n", "" } # Kubernetes Version is 2 lines below "Kubernetes Version" if ($line -match "Kubernetes Version\s*$") { $k8sVersion = [string]$clusterSummaryRaw[$i + 2] -replace "`r", "" -replace "`n", "" } } # Extract Compatibility Check and Set Status Class $compatibilityCheck = "Unknown" $compatibilityClass = "unknown" if ($clusterSummaryText -match "⚠️\s+(Cluster is running an outdated version:[^\n]+)") { $compatibilityCheck = $matches[1].Trim() $compatibilityClass = "warning" } elseif ($clusterSummaryText -match "✅ Cluster is up to date \((.*?)\)") { $compatibilityCheck = "✅ Cluster is up to date ($matches[1])" $compatibilityClass = "healthy" } # Extract numerical data with improved regex $totalNodes = Extract-Metric "🚀 Nodes" $clusterSummaryText $healthyNodes = Extract-Metric "🟩 Healthy" $clusterSummaryText $issueNodes = Extract-Metric "🟥 Issues" $clusterSummaryText $totalPods = Extract-Metric "📦 Pods" $clusterSummaryText $runningPods = Extract-Metric "🟩 Running" $clusterSummaryText $failedPods = Extract-Metric "🟥 Failed" $clusterSummaryText $totalRestarts = Extract-Metric "🔄 Restarts" $clusterSummaryText $warnings = Extract-Metric "🟨 Warnings" $clusterSummaryText $critical = Extract-Metric "🟥 Critical" $clusterSummaryText $pendingPods = Extract-Metric "⏳ Pending Pods" $clusterSummaryText $stuckPods = Extract-Metric "⚠️ Stuck Pods" $clusterSummaryText $jobFailures = Extract-Metric "📉 Job Failures" $clusterSummaryText # **Extract Warnings & Errors from Events** $eventWarnings = Extract-Metric "⚠️ Warnings" $clusterSummaryText $eventErrors = Extract-Metric "❌ Errors" $clusterSummaryText # Extract Pod Distribution $podAvg = if ($clusterSummaryText -match "📊 Pod Distribution: Avg: ([\d.]+)") { $matches[1] } else { "0" } $podMax = if ($clusterSummaryText -match "Max: ([\d.]+)") { $matches[1] } else { "0" } $podMin = if ($clusterSummaryText -match "Min: ([\d.]+)") { $matches[1] } else { "0" } $podTotalNodes = if ($clusterSummaryText -match "Total Nodes: ([\d]+)") { $matches[1] } else { "0" } # Extract CPU and Memory Usage $cpuUsage = if ($clusterSummaryText -match "🖥 CPU Usage:\s*([\d.]+)%") { [double]$matches[1] } else { 0 } $cpuStatus = if ($clusterSummaryText -match "🖥 CPU Usage:.*(🟩 Normal|🟡 Warning|🔴 Critical)") { $matches[1] } else { "Unknown" } $memUsage = if ($clusterSummaryText -match "💾 Memory Usage:\s*([\d.]+)%") { [double]$matches[1] } else { 0 } $memStatus = if ($clusterSummaryText -match "💾 Memory Usage:.*(🟩 Normal|🟡 Warning|🔴 Critical)") { $matches[1] } else { "Unknown" } # # Debugging: Print extracted values # Write-Host "📊 Extracted Values:" # Write-Host "Cluster Name: $clusterName" # Write-Host "Kubernetes Version: $k8sVersion" # Write-Host "Compatibility: $compatibilityCheck" # Write-Host "Nodes: $totalNodes, Healthy: $healthyNodes, Issues: $issueNodes" # Write-Host "Pods: $totalPods, Running: $runningPods, Failed: $failedPods" # Write-Host "Restarts: $totalRestarts, Warnings: $warnings, Critical: $critical" # Write-Host "Pending Pods: $pendingPods, Stuck Pods: $stuckPods, Job Failures: $jobFailures" # Write-Host "Pod Distribution -> Avg: $podAvg, Max: $podMax, Min: $podMin, Total Nodes: $podTotalNodes" # Write-Host "CPU Usage: $cpuUsage%, Status: $cpuStatus" # Write-Host "Memory Usage: $memUsage%, Status: $memStatus" # Prepare the dynamic date $today = (Get-Date).ToUniversalTime().ToString("MMMM dd, yyyy HH:mm:ss 'UTC'") $year = (Get-Date).ToUniversalTime().ToString("yyyy") # Get thresholds from config or use defaults $thresholds = Get-KubeBuddyThresholds -Silent # Define classes based on config-defined thresholds $errorClass = if ($eventErrors -ge $thresholds.event_errors_critical) { "critical" } ` elseif ($eventErrors -ge $thresholds.event_errors_warning) { "warning" } ` else { "normal" } $warningClass = if ($eventWarnings -ge $thresholds.event_warnings_critical) { "critical" } ` elseif ($eventWarnings -ge $thresholds.event_warnings_warning) { "warning" } ` else { "normal" } $cpuClass = if ($cpuUsage -ge $thresholds.cpu_critical) { "critical" } ` elseif ($cpuUsage -ge $thresholds.cpu_warning) { "warning" } ` else { "normal" } $memClass = if ($memUsage -ge [double]$thresholds.mem_critical) { "critical" } ` elseif ($memUsage -ge [double]$thresholds.mem_warning) { "warning" } ` else { "normal" } # Build the HTML Template $htmlTemplate = @" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Kubernetes Cluster Report</title> <style> @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap'); html { scroll-behavior: smooth; /* optional smooth scrolling */ } body { font-family: 'Roboto', sans-serif; margin: 0; padding: 0; background: #eceff1; color: #37474f; } .header { background-color: #0071FF; color: white; display: flex; justify-content: space-between; align-items: flex-start; padding: 20px; font-weight: bold; font-size: 24px; } .container { max-width: 1350px; margin: 20px auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); } .compatibility { padding: 12px; border-radius: 5px; font-weight: bold; text-align: center; color: #ffffff; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); } /* Colors based on cluster status */ .warning { background: #ffeb3b; } /* Yellow */ .healthy { background: #4CAF50; } /* Green */ .unknown { background: #9E9E9E; } /* Gray */ .table-container { overflow-x: auto; /* Enables horizontal scrolling */ width: 100%; max-width: 100%; } table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 14px; text-align: left; } table, th, td { white-space: nowrap; /* Prevents text from wrapping */ border: 1px solid #cfd8dc; padding: 12px; text-align: left; } th { background-color: #0071FF; color: white; } /* “Back to Top” button style */ #backToTop { position: fixed; bottom: 20px; right: 20px; background-color: #0071FF; color: #fff; padding: 10px 15px; border-radius: 5px; text-decoration: none; font-size: 14px; font-weight: bold; box-shadow: 0 2px 6px rgba(0,0,0,0.3); display: none; /* hide by default */ transition: opacity 0.3s ease; } #backToTop:hover { background-color: #005ad1; } #printContainer { text-align: right; margin-bottom: 15px; } #printContainer button { background-color: #0071FF; color: white; padding: 10px 15px; border: none; cursor: pointer; font-size: 16px; border-radius: 5px; } #printContainer button:hover { background-color: #005ad1; } #savePdfBtn { background-color: #0071FF; color: white; padding: 8px 12px; font-size: 14px; font-weight: bold; border: none; cursor: pointer; border-radius: 5px; margin-top: 10px; } #savePdfBtn:hover { background-color: #005ad1; } @media print { #savePdfBtn { display: none; } /* Hide button in PDF */ table { width: 100%; table-layout: fixed; border-collapse: collapse; } th, td { white-space: normal !important; overflow: visible !important; word-wrap: break-word; padding: 8px; border: 1px solid #ddd; } .table-container { overflow: visible !important; height: auto !important; } } /* Hide button when printing */ @media print { #printContainer { display: none; } details { display: block; } /* Expand all details on print */ } /* Floating TOC (Initially Collapsed) */ #toc { position: fixed; top: 50%; left: 0; transform: translateY(-50%); background-color: #0071FF; /* Fully blue when collapsed */ border: none; border-radius: 0 8px 8px 0; box-shadow: 2px 2px 8px rgba(0,0,0,0.15); width: 40px; /* Small when collapsed */ height: 100px; /* Ensures the blue part covers the visible section */ transition: all 0.3s ease-in-out; overflow: hidden; z-index: 9999; display: flex; align-items: center; justify-content: center; } /* Menu Button (Always Visible in Collapsed State) */ #toc-toggle { color: white; padding: 15px 5px; writing-mode: vertical-rl; text-align: center; font-weight: bold; cursor: pointer; user-select: none; display: flex; align-items: center; justify-content: center; width: 100%; /* Takes full width when collapsed */ } /* Expanded TOC */ #toc.open { background-color: white; width: 200px; /* Expanded width */ height: auto; padding: 10px; } /* Hide the blue when expanded */ #toc.open #toc-toggle { display: none; } /* TOC Content (Initially Hidden) */ #toc-content { display: none; padding: 10px; } /* Show TOC Content When Open */ #toc.open #toc-content { display: block; } /* Close Button */ #toc-close { position: absolute; top: 5px; right: 5px; background: none; border: none; color: #37474f; font-size: 16px; font-weight: bold; cursor: pointer; } /* TOC Links Styling */ #toc ul { list-style: none; margin: 0; padding: 0; } #toc ul li { margin: 8px 0; } #toc ul li a { display: block; color: #37474f; text-decoration: none; font-size: 14px; padding: 5px 0; transition: color 0.2s ease-in-out; } #toc ul li a:hover { color: #0071FF; } /* Nested Lists (Indentation) */ #toc ul li details ul { padding-left: 15px; } /* Auto-close menu on small screens */ @media(max-width: 800px) { #toc { display: none; } } details ul { margin-left: 1.5em; } .hero-metrics { display: flex; justify-content: space-around; margin-bottom: 20px; flex-wrap: wrap; } .metric-card { text-align: center; padding: 20px; border-radius: 8px; color: white; font-size: 20px; font-weight: bold; min-width: 150px; flex: 1; margin: 10px; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); } /* Dynamic Colors */ .normal { background-color: #388e3c; } /* Green (Healthy) */ .warning { background-color: #ffa000; } /* Orange (Warning) */ .critical { background-color: #B71C1C; } /* Red (Critical) */ .default { background-color: #0071FF; } /* Responsive Adjustments */ @media (max-width: 600px) { .hero-metrics { flex-direction: column; align-items: center; } .metric-card { width: 80%; } } .footer { background-color: #263238; color: white; text-align: center; padding: 15px 20px; font-size: 14px; position: relative; /* This allows the footer to stay below content */ } .footer a { color: #80cbc4; text-decoration: none; } .footer a:hover { text-decoration: underline; } </style> </head> <body> <div id="toc"> <div id="toc-toggle">MENU</div> <!-- Collapsed button --> <div id="toc-content"> <button id="toc-close">✖</button> <!-- Close Button --> <h3>Sections</h3> <ul> <li><a href="#summary">Cluster Summary</a></li> <!-- Collapsible "Nodes" section --> <li> <details> <summary>Nodes</summary> <ul> <li><a href="#nodecon">Node Conditions</a></li> <li><a href="#noderesource">Node Resources</a></li> </ul> </details> </li> <li><a href="#namespaces">Namespaces</a></li> <!-- Collapsible "Workloads" section --> <li> <details> <summary>Workloads</summary> <ul> <li><a href="#daemonsets">DaemonSets</a></li> </ul> </details> </li> <!-- Collapsible "Pods" section --> <li> <details> <summary>Pods</summary> <ul> <li><a href="#podrestarts">Pods with High Restarts</a></li> <li><a href="#podlong">Long Running Pods</a></li> <li><a href="#podfail">Failed Pods</a></li> <li><a href="#podpend">Pending Pods</a></li> <li><a href="#crashloop">Pods in Crashloop</a></li> <li><a href="#debugpods">Running Debug Pods</a></li> </ul> </details> </li> <!-- Collapsible "Jobs" section --> <li> <details> <summary>Jobs</summary> <ul> <li><a href="#stuckjobs">Stuck Jobs</a></li> <li><a href="#failedjobs">Job Failures</a></li> </ul> </details> </li> <!-- Collapsible "Networking" section --> <li> <details> <summary>Networking</summary> <ul> <li><a href="#servicenoendpoints">Services without Endpoints</a></li> </ul> </details> </li> <!-- Collapsible "Storage" section --> <li> <details> <summary>Storage</summary> <ul> <li><a href="#unmountedpv">Unmounted Persistent Volumes</a></li> </ul> </details> <!-- Collapsible "Security" section --> <li> <details> <summary>Security</summary> <ul> <li><a href="#rbacmisconfig">RBAC Misconfigurations</a></li> <li><a href="#orphanedconfigmaps">Orphaned ConfigMaps</a></li> <li><a href="#orphanedsecrets">Orphaned Secrets</a></li> </ul> </details> <li><a href="#clusterwarnings">Kubernetes Events</a></li> $aksMenuItem </ul> </div> </div> <div id="top"></div> <div class="header"> <div style="display: flex; flex-direction: column;"> <span>Kubernetes Cluster Report: $clusterName</span> <span style="font-size: 12px;"> Powered by <img src="https://raw.githubusercontent.com/KubeDeckio/KubeBuddy/refs/heads/main/images/reportheader%20(2).png" alt="KubeBuddy Logo" style="height: 70px; vertical-align: middle;"> </span> </div> <!-- Right side: date & "Created by" under a line --> <div style="text-align: right;"> <p style="margin: 0; font-size: 14px;"> Generated on: <strong>$today</strong> </p> <hr style="border: 0; border-top: 1px solid #fff; margin: 6px 0;" /> <p style="margin: 0; font-size: 14px;"> Created by: <a href="https://kubedeck.io" target="_blank" style="color: #fff; text-decoration: none;"> 🌐 kubedeck.io </a> </p> <!-- Save as PDF Button --> <div id="printContainer"> <button id="savePdfBtn">📄 Save as PDF</button> </div> </div> </div> </div> <div class="container"> <h1 id="summary">Cluster Summary</h1> <p><strong>Cluster Name:</strong> $clusterName</p> <p><strong>Kubernetes Version:</strong> $k8sVersion</p> <div class="compatibility $compatibilityClass"><strong>$compatibilityCheck</strong></div> <h2>Cluster Metrics Summary</h2> <table> <tr> <td>🚀 Nodes: $totalNodes</td> <td>🟩 Healthy: $healthyNodes</td> <td>🟥 Issues: $issueNodes</td> </tr> <tr> <td>📦 Pods: $totalPods</td> <td>🟩 Running: $runningPods</td> <td>🟥 Failed: $failedPods</td> </tr> <tr> <td>🔄 Restarts: $totalRestarts</td> <td>🟨 Warnings: $warnings</td> <td>🟥 Critical: $critical</td> </tr> <tr> <td>⏳ Pending Pods: $pendingPods</td> <td>🟡 Waiting: $pendingPods</td> <td></td> </tr> <tr> <td>⚠️ Stuck Pods: $stuckPods</td> <td>❌ Stuck: $stuckPods</td> <td></td> </tr> <tr> <td>📉 Job Failures: $jobFailures</td> <td>🔴 Failed: $jobFailures</td> <td></td> </tr> </table> <h2>Pod Distribution</h2> <table> <tr> <td>Avg: <strong>$podAvg</strong></td> <td>Max: <strong>$podMax</strong></td> <td>Min: <strong>$podMin</strong></td> <td>Total Nodes: <strong>$podTotalNodes</strong></td> </tr> </table> <h2>Resource Usage</h2> <div class="hero-metrics"> <div class="metric-card $cpuClass">🖥 CPU: <strong>$cpuUsage%</strong> <br><span>$cpuStatus</span></div> <div class="metric-card $memClass">💾 Memory: <strong>$memUsage%</strong> <br><span>$memStatus</span></div> </div> <!-- Hero Metrics Section --> <h2>Cluster Events</h2> <div class="hero-metrics"> <div class="metric-card $errorClass">❌ Errors: <strong>$eventErrors</strong></div> <div class="metric-card $warningClass">⚠️ Warnings: <strong>$eventWarnings</strong></div> </div> </div> </div> <!-- Node Conditions Container --> <div class="container"> <h1>Node Conditions & Resources</h1> <h2 id="nodecon">Node Conditions</h2> <div class="table-container"> $collapsibleNodeSection </div> <h2 id="noderesource">Node Resources</h2> <div class="table-container"> $collapsibleNodeResources </div> </div> <!-- Namespace Container --> <div class="container"> <h1 id="namespaces">Namespaces</h1> <h2>Empty Namespaces</h2> <div class="table-container"> $collapsibleEmptyNsHtmls </div> </div> <!-- Workload Container --> <div class="container"> <h1 id="workloads">Workloads</h1> <h2 id=daemonsets>DaemonSets Not Fully Running</h2> <div class="table-container"> $collapsibleDsIssuesHtml </div> </div> <!-- Pods Container --> <div class="container"> <h1 id="pods">Pods</h1> <h2 id="podrestarts">Pods with High Restarts</h2> <div class="table-container"> $collapsiblePodsRestartHtml </div> <h2 id="podlong">Long Running Pods</h2> <div class="table-container"> $collapsiblePodLongRunningHtml </div> <h2 id="podfail">Failed Pods</h2> <div class="table-container"> $collapsiblePodFailHtml </div> <h2 id="podpending">Pending Pods</h2> <div class="table-container"> $collapsiblePodPendingHtml </div> <h2 id="crashloop">CrashLoopBackOff Pods</h2> <div class="table-container"> $collapsibleCrashloopHtml </div> <h2 id="debugpods">Running Debug Pods</h2> <div class="table-container"> $collapsibleLeftoverdebugHtml </div> </div> <!-- Job Container --> <div class="container"> <h1 id="jobs">Jobs</h1> <h2 id="stuckjobs">Stuck Jobs</h2> <div class="table-container"> $collapsibleStuckJobHtml </div> <h2 id="failedjobs">Job Failures</h2> <div class="table-container"> $collapsibleJobFailHtml </div> </div> <!-- Networking Container --> <div class="container"> <h1 id="jobs">Networking</h1> <h2 id="servicenoendpoints">Services without Endpoints</h2> <div class="table-container"> $collapsibleServicesWithoutEndpointsHtml </div> </div> <!-- Storage Container --> <div class="container"> <h1 id="storage">Storage</h1> <h2 id="unmountedpv">Unmounted Persistent Volumes</h2> <div class="table-container"> $collapsibleUnmountedpvHtml </div> </div> <!-- Security Container --> <div class="container"> <h1 id="security">Security</h1> <h2 id="rbacmisconfig">RBAC Misconfigurations</h2> <div class="table-container"> $collapsibleRbacmisconfigHtml </div> <h2 id="orphanedconfigmaps">Orphaned ConfigMaps</h2> <div class="table-container"> $collapsibleOrphanedConfigMapsHtml </div> <h2 id="orphanedsecrets">Orphaned Secrets</h2> <div class="table-container"> $collapsibleOrphanedSecretsHtml </div> </div> <!-- Kube Event Container --> <div class="container"> <h1 id="kubeevents">Kubernetes Warning Events</h1> <h2 id="clusterwarnings">Recent Cluster Warnings</h2> <div class="table-container"> $collapsibleEventSummaryHtml </div> </div> $aksHealthCheck <footer class="footer"> <p><strong>Report generated by Kubebuddy $version</strong> on $today</p> <p>© $year Kubebuddy | <a href="https://kubedeck.io" target="_blank">KubeDeck.io</a></p> <p><em>This report is a snapshot of the cluster state at the time of generation. It may not reflect real-time changes. Always verify configurations before making critical decisions.</em></p> </footer> <!-- Back to Top Button --> <a href="#top" id="backToTop">Back to Top</a> <script> // Show/hide "Back to Top" button based on scroll window.addEventListener('scroll', function() { const button = document.getElementById('backToTop'); // If scrolled down more than 200px, show the button, else hide it if (window.scrollY > 200) { button.style.display = 'block'; } else { button.style.display = 'none'; } }); document.addEventListener("DOMContentLoaded", function() { const toc = document.getElementById("toc"); const tocToggle = document.getElementById("toc-toggle"); const tocContent = document.getElementById("toc-content"); const tocClose = document.getElementById("toc-close"); // Start in collapsed state toc.classList.remove("open"); tocContent.style.display = "none"; tocToggle.addEventListener("click", function() { toc.classList.add("open"); tocContent.style.display = "block"; // Show content toc.style.width = "200px"; // Expand toc.style.height = "auto"; }); tocClose.addEventListener("click", function() { toc.classList.remove("open"); tocContent.style.display = "none"; // Hide content toc.style.width = "40px"; // Shrink toc.style.height = "100px"; // Keep blue part covering it }); // Auto-close TOC on scroll let lastScrollY = window.scrollY; window.addEventListener("scroll", function() { if (Math.abs(window.scrollY - lastScrollY) > 50) { // If scrolled more than 50px toc.classList.remove("open"); tocContent.style.display = "none"; toc.style.width = "40px"; toc.style.height = "100px"; } lastScrollY = window.scrollY; }); }); document.getElementById("savePdfBtn").addEventListener("click", function() { // Store original open state of all collapsible sections const detailsElements = document.querySelectorAll("details"); const detailsStates = new Map(); detailsElements.forEach(detail => { detailsStates.set(detail, detail.open); detail.open = true; // Expand all sections }); // Save original table styles const tableContainers = document.querySelectorAll(".table-container"); const tables = document.querySelectorAll("table"); const originalStyles = []; tableContainers.forEach((container, index) => { originalStyles[index] = { overflow: container.style.overflow, height: container.style.height }; // Remove scrollbars and expand tables container.style.overflow = "visible"; container.style.height = "auto"; }); // Force table rows to expand fully tables.forEach(table => { table.style.width = "100%"; table.style.tableLayout = "fixed"; }); // Delay print to allow rendering setTimeout(() => { window.print(); }, 500); // Restore original styles after printing window.onafterprint = function() { detailsElements.forEach(detail => { detail.open = detailsStates.get(detail); // Restore original state }); tableContainers.forEach((container, index) => { container.style.overflow = originalStyles[index].overflow; container.style.height = originalStyles[index].height; }); // Reset table styles tables.forEach(table => { table.style.tableLayout = ""; }); }; }); </script> </body> </html> "@ # Save the updated HTML report $htmlTemplate | Set-Content $outputPath } # Example usage: # Generate-K8sHTMLReport -outputPath "output-report.html" |