diff --git a/ECMJobRunner.WebCron/Program.cs b/ECMJobRunner.WebCron/Program.cs
index 80e0ce8..1f40d1b 100644
--- a/ECMJobRunner.WebCron/Program.cs
+++ b/ECMJobRunner.WebCron/Program.cs
@@ -5,10 +5,12 @@ using ECMJobRunner.WebCron.ProfileWorker;
using Hangfire;
using Hangfire.SqlServer;
using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
using Serilog;
using Serilog.Ui.Core.Extensions;
using Serilog.Ui.SqliteDataProvider.Extensions;
using Serilog.Ui.Web.Extensions;
+using System.Text;
// Enable Serilog self-diagnostics
Serilog.Debugging.SelfLog.Enable(msg => Console.WriteLine($"[SERILOG] {msg}"));
@@ -138,6 +140,19 @@ app.UseHangfireDashboard("/hangfire", new DashboardOptions
Authorization = new[] { new AllowAllDashboardAuthorizationFilter() }
});
+// Add Health Check UI route (accessible outside Hangfire dashboard)
+app.MapGet("/health-ui", async (HealthCheckService healthCheckService, HttpContext context) =>
+{
+ var report = await healthCheckService.CheckHealthAsync(context.RequestAborted);
+
+ var html = GenerateHealthCheckHtml(report);
+
+ context.Response.ContentType = "text/html";
+ await context.Response.WriteAsync(html);
+
+ return Results.Empty;
+});
+
// Add Serilog.UI Dashboard
app.UseSerilogUi();
@@ -186,3 +201,305 @@ finally
{
Log.CloseAndFlush();
}
+
+// Helper method to generate health check HTML
+static string GenerateHealthCheckHtml(HealthReport report)
+{
+ var sb = new StringBuilder();
+
+ sb.AppendLine(@"
+
+
+
+
+
+ Health Check - ECMJobRunner
+
+
+
+
+");
+
+ // Navigation
+ sb.Append(@"
+
+
");
+
+ // Overall Status Header
+ var statusClass = report.Status switch
+ {
+ HealthStatus.Healthy => "status-healthy",
+ HealthStatus.Degraded => "status-degraded",
+ HealthStatus.Unhealthy => "status-unhealthy",
+ _ => ""
+ };
+
+ var badgeClass = report.Status switch
+ {
+ HealthStatus.Healthy => "badge-healthy",
+ HealthStatus.Degraded => "badge-degraded",
+ HealthStatus.Unhealthy => "badge-unhealthy",
+ _ => "badge-secondary"
+ };
+
+ var icon = report.Status switch
+ {
+ HealthStatus.Healthy => "✅",
+ HealthStatus.Degraded => "⚠️",
+ HealthStatus.Unhealthy => "❌",
+ _ => "❓"
+ };
+
+ sb.Append($@"
+
+
+
+
");
+ sb.Append(icon);
+ sb.Append($@" Health Check Status
+
Last checked: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC
+
+
+
{report.Status}
+
+
+
+
+
+
+
+
");
+ sb.AppendLine();
+ sb.Append(" 📊 Summary");
+ sb.AppendLine();
+ sb.Append($@"
+
+
+ Overall Status:
+ {report.Status}
+
+
+ Total Duration:
+ {report.TotalDuration.TotalMilliseconds:F0} ms
+
+
+ Checks:
+ {report.Entries.Count}
+
+
+
+
+
+
");
+
+ // Individual Checks
+ foreach (var entry in report.Entries)
+ {
+ var checkStatusClass = entry.Value.Status switch
+ {
+ HealthStatus.Healthy => "status-healthy",
+ HealthStatus.Degraded => "status-degraded",
+ HealthStatus.Unhealthy => "status-unhealthy",
+ _ => ""
+ };
+
+ var checkBadgeClass = entry.Value.Status switch
+ {
+ HealthStatus.Healthy => "badge-healthy",
+ HealthStatus.Degraded => "badge-degraded",
+ HealthStatus.Unhealthy => "badge-unhealthy",
+ _ => "badge-secondary"
+ };
+
+ var checkIcon = entry.Value.Status switch
+ {
+ HealthStatus.Healthy => "✅",
+ HealthStatus.Degraded => "⚠️",
+ HealthStatus.Unhealthy => "❌",
+ _ => "❓"
+ };
+
+ 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())
+ {
+ sb.AppendLine(@"
+
+
Details:
+
");
+
+ foreach (var data in entry.Value.Data)
+ {
+ var value = data.Value?.ToString() ?? "N/A";
+
+ // Format DateTime values
+ if (data.Value is DateTime dt)
+ {
+ value = dt.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+ else if (data.Value is TimeSpan ts)
+ {
+ value = $"{ts.TotalSeconds:F1}s";
+ }
+
+ sb.AppendLine($@"
+
+ | {data.Key} |
+ {value} |
+
");
+ }
+
+ sb.AppendLine(@"
+
+
");
+ }
+
+ sb.AppendLine(@"
+
+
");
+ }
+
+ sb.AppendLine(@"
+
+
+
+
+ ");
+ sb.AppendLine();
+ sb.Append(" Auto-refresh in 10s");
+ sb.AppendLine();
+ sb.AppendLine(@"
+
+
+");
+
+ return sb.ToString();
+}