feat(health): show all cluster nodes (online/offline, primary/standby) in health dashboard

Add NodeStatus record, IClusterNodeProvider interface, and AkkaClusterNodeProvider
that queries Akka cluster membership for all site-role nodes. HealthReportSender
populates ClusterNodes before each report. UI shows a row per node with
hostname, Online/Offline badge, and Primary/Standby badge. Falls back to
single-node display if ClusterNodes is not populated.
This commit is contained in:
Joseph Doherty
2026-03-23 14:54:59 -04:00
parent 65cc7b69cd
commit 02a7e8abc6
9 changed files with 127 additions and 8 deletions
@@ -18,6 +18,7 @@ public class HealthReportSender : BackgroundService
private readonly ILogger<HealthReportSender> _logger;
private readonly string _siteId;
private readonly StoreAndForwardStorage? _sfStorage;
private readonly IClusterNodeProvider? _clusterNodeProvider;
private long _sequenceNumber;
public HealthReportSender(
@@ -26,7 +27,8 @@ public class HealthReportSender : BackgroundService
IOptions<HealthMonitoringOptions> options,
ILogger<HealthReportSender> logger,
ISiteIdentityProvider siteIdentityProvider,
StoreAndForwardStorage? sfStorage = null)
StoreAndForwardStorage? sfStorage = null,
IClusterNodeProvider? clusterNodeProvider = null)
{
_collector = collector;
_transport = transport;
@@ -34,6 +36,7 @@ public class HealthReportSender : BackgroundService
_logger = logger;
_siteId = siteIdentityProvider.SiteId;
_sfStorage = sfStorage;
_clusterNodeProvider = clusterNodeProvider;
}
/// <summary>
@@ -58,6 +61,15 @@ public class HealthReportSender : BackgroundService
if (!_collector.IsActiveNode)
continue;
if (_clusterNodeProvider != null)
{
try
{
_collector.SetClusterNodes(_clusterNodeProvider.GetClusterNodes());
}
catch { /* Non-fatal */ }
}
if (_sfStorage != null)
{
try