using Microsoft.Extensions.Diagnostics.HealthChecks; using System.Text; namespace ECMJobRunner.WebCron.HealthCheck; /// /// Generates HTML representation of health check reports. /// public static class HealthCheckHtmlGenerator { public static string Generate(HealthReport report) { var sb = new StringBuilder(); sb.AppendLine(@" Health Check - ECMJobRunner "); // Navigation AppendNavigation(sb); // Overall Status Header AppendOverallStatus(sb, report); // Summary Card AppendSummaryCard(sb, report); // Individual Checks AppendIndividualChecks(sb, report); // Refresh Info AppendRefreshInfo(sb); sb.AppendLine(@" "); return sb.ToString(); } private static void AppendNavigation(StringBuilder sb) { sb.Append(@"
"); // Append emojis separately (not in verbatim string) sb.AppendLine(); sb.Append(" 🏠 Home"); sb.AppendLine(); sb.Append(" 🔧 Hangfire Dashboard"); sb.AppendLine(); sb.Append(" 📋 Logs (Serilog.UI)"); sb.AppendLine(); sb.Append(" 📊 Health API"); sb.AppendLine(); sb.AppendLine("
"); } private static void AppendOverallStatus(StringBuilder sb, HealthReport report) { var statusClass = GetStatusClass(report.Status); var badgeClass = GetBadgeClass(report.Status); var icon = GetStatusIcon(report.Status); sb.Append($@"

"); sb.Append(icon); sb.Append($@" Health Check Status

Last checked: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC

{report.Status}

"); } private static void AppendSummaryCard(StringBuilder sb, HealthReport report) { var statusClass = GetStatusClass(report.Status); sb.Append(@"
"); sb.AppendLine(); sb.Append(" 📊 Summary"); sb.AppendLine(); sb.Append($@"
Overall Status: {report.Status}
Total Duration: {report.TotalDuration.TotalMilliseconds:F0} ms
Checks: {report.Entries.Count}
"); } private static void AppendIndividualChecks(StringBuilder sb, HealthReport report) { foreach (var entry in report.Entries) { var checkStatusClass = GetStatusClass(entry.Value.Status); var checkBadgeClass = GetBadgeClass(entry.Value.Status); var checkIcon = GetStatusIcon(entry.Value.Status); sb.Append($@"
"); sb.Append(checkIcon); sb.Append($@" {entry.Key} {entry.Value.Status}
Description: {entry.Value.Description ?? "N/A"}
Duration: {entry.Value.Duration.TotalMilliseconds:F0} ms
"); if (entry.Value.Exception != null) { sb.AppendLine($@"
Exception: {System.Net.WebUtility.HtmlEncode(entry.Value.Exception.Message)}
"); } if (entry.Value.Data.Any()) { AppendCheckDetails(sb, entry.Value.Data); } sb.AppendLine(@"
"); } sb.AppendLine(@"
"); } private static void AppendCheckDetails(StringBuilder sb, IReadOnlyDictionary data) { sb.AppendLine(@"
Details: "); foreach (var item in data) { var value = FormatDataValue(item.Value); sb.AppendLine($@" "); } sb.AppendLine(@"
{item.Key} {value}
"); } private static void AppendRefreshInfo(StringBuilder sb) { sb.AppendLine(@"
"); sb.AppendLine(); sb.Append(" Auto-refresh in 10s"); sb.AppendLine(); sb.AppendLine(@"
"); } private static string GetStatusClass(HealthStatus status) { return status switch { HealthStatus.Healthy => "status-healthy", HealthStatus.Degraded => "status-degraded", HealthStatus.Unhealthy => "status-unhealthy", _ => "" }; } private static string GetBadgeClass(HealthStatus status) { return status switch { HealthStatus.Healthy => "badge-healthy", HealthStatus.Degraded => "badge-degraded", HealthStatus.Unhealthy => "badge-unhealthy", _ => "badge-secondary" }; } private static string GetStatusIcon(HealthStatus status) { return status switch { HealthStatus.Healthy => "✅", HealthStatus.Degraded => "⚠️", HealthStatus.Unhealthy => "❌", _ => "❓" }; } private static string FormatDataValue(object? value) { if (value == null) return "N/A"; if (value is DateTime dt) return dt.ToString("yyyy-MM-dd HH:mm:ss"); if (value is TimeSpan ts) return $"{ts.TotalSeconds:F1}s"; return value.ToString() ?? "N/A"; } }