using MCVIngenieros.Healthchecks.Abstracts; using Microsoft.AspNetCore.Mvc; using System.ComponentModel; using System.Drawing; using System.Net.Mime; namespace MCVIngenieros.Healthchecks.__.__Controllers; [ApiController, Route("api/health"), EditorBrowsable(EditorBrowsableState.Never)] public sealed class HealthCheckController(IHealthCheckContainer container, ILogger logger) : ControllerBase { private readonly IHealthCheckContainer _container = container; private readonly ILogger _logger = logger; [HttpGet] public async Task Run([FromQuery] string? checks = null, CancellationToken cancellationToken = default) { _logger.LogInformation($"Health check requested for checks: {checks ?? "ALL"}"); List results; if (!string.IsNullOrEmpty(checks)) { var names = checks.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); await _container.RunChecks(names, cancellationToken); } else { await _container.RunAllChecks(cancellationToken); } results = await _container.GetAllChecksResults(cancellationToken); var status = results.All(r => r.IsHealthy) ? "Healthy" : "Unhealthy"; var code = results.All(r => r.IsHealthy) ? 200 : 503; var acceptHeader = Request.Headers.Accept.ToString(); var userAgent = Request.Headers.UserAgent.ToString(); if (IsBrowser(Request)) { return Content(RenderHtml(status, results, _container), MediaTypeNames.Text.Html); } else { var response = new { status, results = results.ToDictionary(r => r.Name, r => new { isHealthy = r.IsHealthy, details = r.Details, severity = r.Severity.ToString() }) }; return StatusCode(code, response); } } [HttpGet, Route("{checkName}")] public async Task Get([FromRoute] string checkName, CancellationToken cancellationToken = default) { var result = await _container.RunCheck(checkName, cancellationToken); if (result == null) { _logger.LogWarning($"Health check '{checkName}' not found."); return NotFound(); } _logger.LogWarning($"Health check requested for {checkName}."); var status = result.IsHealthy ? "Healthy" : "Unhealthy"; var code = result.IsHealthy ? 200 : 503; var response = new { status, result = result == null ? null : new { isHealthy = result.IsHealthy, details = result.Details, severity = result.Severity.ToString() } }; return StatusCode(code, response); } private static bool UserAgentIndicatesBrowser(string userAgent) => !string.IsNullOrEmpty(userAgent) && ( userAgent.Contains("Mozilla", StringComparison.OrdinalIgnoreCase) || userAgent.Contains("Chrome", StringComparison.OrdinalIgnoreCase) || userAgent.Contains("Safari", StringComparison.OrdinalIgnoreCase) || userAgent.Contains("Edge", StringComparison.OrdinalIgnoreCase) ); private static bool AcceptsHtml(string acceptHeader) => !string.IsNullOrEmpty(acceptHeader) && acceptHeader.Contains("text/html", StringComparison.OrdinalIgnoreCase); private static bool IsBrowser(HttpRequest request) => AcceptsHtml(request.Headers["Accept"].ToString()) || UserAgentIndicatesBrowser(request.Headers["User-Agent"].ToString()); private static string GetSeverityClases() { var severitiesDict = Enum.GetValues() .Cast() .ToDictionary(e => (int)e, e => e.ToString()); var totalSeverities = severitiesDict.Count; var colores = ColorInterpolation.GenerateColorGradient(totalSeverities); string cssClasses = string.Empty; int i = 0; foreach (var item in severitiesDict) { string cssClass = $".{item.Value}{{ color: {colores[i++]}; font-weight: bold; }}"; cssClasses += cssClass + Environment.NewLine; } return cssClasses; } // Render HTML page for browser private static string RenderHtml(string status, List results, IHealthCheckContainer container) { var severitiesDict = Enum.GetValues() .Cast() .ToDictionary(e => (int)e, e => e.ToString()); var healthy = status == "Healthy"; var bannerColor = healthy ? "#4caf50" : "#f44336"; var html = $@" Health Checks
Health Status: {status}

Health Check Results

{string.Join("", results.Select((r, idx) => $"" + @$"" + $"" + $"" + @$"" + $"" + ""))}
SelectedNameStatusSeverityDetails
{r.Name.Replace("HealthCheck", "", StringComparison.OrdinalIgnoreCase)}{(r.IsHealthy ? "Healthy" : "Unhealthy")}{r.Severity}{System.Net.WebUtility.HtmlEncode(r.Details)}

Run Selected Checks

×

"; return html; } public class ColorInterpolation { // Convierte un código hex a Color private static Color HexToColor(string hex) { return ColorTranslator.FromHtml(hex); } // Convierte Color a código hex private static string ColorToHex(Color color) { return $"#{color.R:X2}{color.G:X2}{color.B:X2}"; } // Interpola entre dos colores según factor (0 a 1) private static Color InterpolateColor(Color start, Color end, double factor) { int r = (int)(start.R + factor * (end.R - start.R)); int g = (int)(start.G + factor * (end.G - start.G)); int b = (int)(start.B + factor * (end.B - start.B)); return Color.FromArgb(r, g, b); } // Genera una lista de colores interpolados para n valores public static List GenerateColorGradient(int n) { var startColor = HexToColor("#4caf50"); // verde var midColor = HexToColor("#e5be01"); // amarillo var endColor = HexToColor("#f44336"); // rojo var colors = new List(n); for (int i = 0; i < n; i++) { double factor = (double)i / (n - 1); if (factor <= 0.5) { double localFactor = factor / 0.5; var c = InterpolateColor(startColor, midColor, localFactor); colors.Add(ColorToHex(c)); } else { double localFactor = (factor - 0.5) / 0.5; var c = InterpolateColor(midColor, endColor, localFactor); colors.Add(ColorToHex(c)); } } return colors; } } }