feat(adminui): /fleet shows 'Applied (degraded)', /deployments 'Sealed (degraded)' (#601)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 17:34:58 -04:00
parent 49dfabc141
commit f066fb9c80
2 changed files with 22 additions and 12 deletions
@@ -57,7 +57,8 @@
<tr>
<td><code>@Short(d.DeploymentId)</code></td>
<td><code>@DisplayText.Abbreviate(d.RevisionHash, 12)</code></td>
<td>@d.Status</td>
@* #601: Sealed + a reason = sealed with a degraded address-space apply on some node(s). *@
<td title="@(d.FailureReason ?? "")">@(d.Status == DeploymentStatus.Sealed && !string.IsNullOrEmpty(d.FailureReason) ? "Sealed (degraded)" : d.Status.ToString())</td>
<td>@d.CreatedBy</td>
<td>@d.CreatedAtUtc.ToString("u")</td>
<td>@(d.SealedAtUtc?.ToString("u") ?? "—")</td>
@@ -77,7 +78,8 @@
DeploymentStatus Status,
string CreatedBy,
DateTime CreatedAtUtc,
DateTime? SealedAtUtc);
DateTime? SealedAtUtc,
string? FailureReason);
private IReadOnlyList<DeploymentRow> _deployments = Array.Empty<DeploymentRow>();
private bool _busy;
@@ -102,7 +104,8 @@
d.Status,
d.CreatedBy,
d.CreatedAtUtc,
d.SealedAtUtc))
d.SealedAtUtc,
d.FailureReason))
.Take(50)
.ToListAsync();
@@ -88,7 +88,7 @@ else
<span class="chip chip-idle me-1">@role</span>
}
</td>
<td><span class="chip @StatusChipClass(r.Status)">@StatusLabel(r.Status)</span></td>
<td><span class="chip @StatusChipClass(r.Status, r.FailureReason)">@StatusLabel(r.Status, r.FailureReason)</span></td>
<td>@(r.AppliedAtUtc?.ToString("u") ?? "—")</td>
<td>@(r.StartedAtUtc?.ToString("u") ?? "—")</td>
<td title="@(r.FailureReason ?? "")">@(string.IsNullOrEmpty(r.FailureReason) ? "" : DisplayText.Abbreviate(r.FailureReason, 80))</td>
@@ -184,15 +184,22 @@ else
}
}
private static string StatusChipClass(NodeDeploymentStatus? status) => status switch
{
NodeDeploymentStatus.Applied => "chip-ok",
NodeDeploymentStatus.Applying => "chip-caution",
NodeDeploymentStatus.Failed => "chip-alert",
_ => "chip-idle",
};
// #601 — an Applied row carrying a reason is a DEGRADED apply (the node runs the revision but its address
// space may be partial); it must not render as the same green chip as a clean apply.
private static bool IsDegraded(NodeDeploymentStatus? status, string? failureReason) =>
status is { } s && ZB.MOM.WW.OtOpcUa.ControlPlane.Coordinators.ConfigPublishCoordinator.IsDegraded(s, failureReason);
private static string StatusLabel(NodeDeploymentStatus? status) => status?.ToString() ?? "waiting";
private static string StatusChipClass(NodeDeploymentStatus? status, string? failureReason) =>
IsDegraded(status, failureReason) ? "chip-caution" : status switch
{
NodeDeploymentStatus.Applied => "chip-ok",
NodeDeploymentStatus.Applying => "chip-caution",
NodeDeploymentStatus.Failed => "chip-alert",
_ => "chip-idle",
};
private static string StatusLabel(NodeDeploymentStatus? status, string? failureReason) =>
IsDegraded(status, failureReason) ? "Applied (degraded)" : status?.ToString() ?? "waiting";
public async ValueTask DisposeAsync()
{