feat(ui): health dashboard shows metrics-stale badge and offline-since timestamp

This commit is contained in:
Joseph Doherty
2026-07-08 16:23:43 -04:00
parent c73b7faa11
commit 87c7255912
2 changed files with 83 additions and 0 deletions
@@ -18,6 +18,7 @@
@inject CommunicationService CommunicationService
@inject IAuditLogQueryService AuditLogQueryService
@inject IKpiHistoryQueryService KpiHistory
@inject Microsoft.Extensions.Options.IOptions<HealthMonitoringOptions> HealthOptions
<div class="container-fluid mt-3">
<div class="d-flex justify-content-between align-items-center mb-3">
@@ -199,6 +200,17 @@
<span class="badge bg-danger me-2" aria-label="State: Offline">@OfflineGlyph Offline</span>
}
<strong class="fs-5">@siteName@(isCentral ? "" : $" ({siteId})")</strong>
@if (state.IsOnline && state.IsMetricsStale)
{
<span class="badge bg-warning text-dark ms-2"
title="Site is heartbeating but has sent no health report for over @StaleTimeoutDisplay — the metrics pipeline may be dead.">
Metrics stale
</span>
}
@if (!state.IsOnline && state.LastStatusChangeAt is { } changedAt)
{
<small class="text-muted ms-2">offline since @changedAt.ToString("u")</small>
}
</div>
<small class="text-muted">
Last report: <TimestampDisplay Value="@state.LastReportReceivedAt" Format="HH:mm:ss" NullText="awaiting first report" />
@@ -419,6 +431,16 @@
private const string PrimaryGlyph = "▲"; // ▲
private const string StandbyGlyph = "△"; // △
// Human-friendly rendering of the configured metrics-stale window for the
// "Metrics stale" badge tooltip (e.g. "2 minutes").
private string StaleTimeoutDisplay =>
FormatDuration(HealthOptions.Value.MetricsStaleTimeout);
private static string FormatDuration(TimeSpan span) =>
span.TotalMinutes >= 1 && span == TimeSpan.FromMinutes(Math.Round(span.TotalMinutes))
? $"{span.TotalMinutes:0} minute{(span.TotalMinutes == 1 ? "" : "s")}"
: $"{span.TotalSeconds:0} seconds";
private IReadOnlyDictionary<string, SiteHealthState> _siteStates = new Dictionary<string, SiteHealthState>();
private Dictionary<string, string> _siteNames = new();
private Timer? _refreshTimer;