fix(health-monitoring): resolve HealthMonitoring-015 — nullable LastReportReceivedAt
A heartbeat-registered site that has never sent a full report now has
LastReportReceivedAt = null instead of the year-0001 sentinel. TimestampDisplay
accepts DateTimeOffset? and renders null as a placeholder ('awaiting first
report') rather than a ~2000-year-stale date. Cross-module: HealthMonitoring +
CentralUI.
This commit is contained in:
@@ -71,7 +71,7 @@
|
||||
<strong class="fs-5">@siteName@(isCentral ? "" : $" ({siteId})")</strong>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
Last report: <TimestampDisplay Value="@state.LastReportReceivedAt" Format="HH:mm:ss" />
|
||||
Last report: <TimestampDisplay Value="@state.LastReportReceivedAt" Format="HH:mm:ss" NullText="awaiting first report" />
|
||||
| Last heartbeat: <TimestampDisplay Value="@state.LastHeartbeatAt" Format="HH:mm:ss" />
|
||||
| Seq: @state.LastSequenceNumber
|
||||
</small>
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
@* Displays a UTC DateTimeOffset formatted for display. Tooltip shows UTC value. *@
|
||||
@* Displays a UTC DateTimeOffset formatted for display. Tooltip shows UTC value.
|
||||
A null Value renders as a plain "never" placeholder — used for timestamps that
|
||||
have not happened yet (e.g. a heartbeat-only site with no full report). *@
|
||||
|
||||
<span title="@Value.UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss") UTC">@Value.LocalDateTime.ToString(Format)</span>
|
||||
@if (Value is { } value)
|
||||
{
|
||||
<span title="@value.UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss") UTC">@value.LocalDateTime.ToString(Format)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">@NullText</span>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired] public DateTimeOffset Value { get; set; }
|
||||
[Parameter, EditorRequired] public DateTimeOffset? Value { get; set; }
|
||||
[Parameter] public string Format { get; set; } = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/// <summary>Text shown when <see cref="Value"/> is null.</summary>
|
||||
[Parameter] public string NullText { get; set; } = "never";
|
||||
}
|
||||
|
||||
@@ -118,12 +118,14 @@ public class CentralHealthAggregator : BackgroundService, ICentralHealthAggregat
|
||||
if (!_siteStates.TryGetValue(siteId, out var existing))
|
||||
{
|
||||
// Unknown site — register it as online, awaiting its first
|
||||
// full report. LatestReport stays null until ProcessReport runs.
|
||||
// full report. LatestReport and LastReportReceivedAt both stay
|
||||
// null until ProcessReport runs — "no report yet" is an explicit
|
||||
// nullable state, not a year-0001 sentinel the UI must special-case.
|
||||
var registered = new SiteHealthState
|
||||
{
|
||||
SiteId = siteId,
|
||||
LatestReport = null,
|
||||
LastReportReceivedAt = default,
|
||||
LastReportReceivedAt = null,
|
||||
LastHeartbeatAt = receivedAt,
|
||||
LastSequenceNumber = 0,
|
||||
IsOnline = true
|
||||
|
||||
@@ -21,10 +21,13 @@ public sealed record SiteHealthState
|
||||
public SiteHealthReport? LatestReport { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Time the latest full <see cref="SiteHealthReport"/> was processed.
|
||||
/// Used by the UI to surface report staleness during failover.
|
||||
/// Time the latest full <see cref="SiteHealthReport"/> was processed, or
|
||||
/// <c>null</c> if the site is known only via heartbeats and has not yet sent
|
||||
/// a report. Used by the UI to surface report staleness during failover;
|
||||
/// the <c>null</c> case must be rendered as "no report yet" rather than as a
|
||||
/// timestamp (a <c>default</c> sentinel would display as year-0001).
|
||||
/// </summary>
|
||||
public DateTimeOffset LastReportReceivedAt { get; init; }
|
||||
public DateTimeOffset? LastReportReceivedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Time the most recent signal of any kind (full report OR heartbeat) was
|
||||
|
||||
Reference in New Issue
Block a user