using Microsoft.EntityFrameworkCore; using ZB.MOM.WW.OtOpcUa.Configuration; using ZB.MOM.WW.OtOpcUa.Configuration.Entities; namespace ZB.MOM.WW.OtOpcUa.Admin.Services; /// /// Read-side service for ClusterNode rows + their cluster-scoped redundancy view. Consumed /// by the RedundancyTab on the cluster detail page. Writes (role swap, node enable/disable) /// are not supported here — role swap happens through the RedundancyCoordinator apply-lease /// flow on the server side and would conflict with any direct DB mutation from Admin. /// public sealed class ClusterNodeService(OtOpcUaConfigDbContext db) { /// Stale-threshold matching HostStatusService.StaleThreshold — 30s of clock /// tolerance covers a missed heartbeat plus publisher GC pauses. public static readonly TimeSpan StaleThreshold = TimeSpan.FromSeconds(30); public Task> ListByClusterAsync(string clusterId, CancellationToken ct) => db.ClusterNodes.AsNoTracking() .Where(n => n.ClusterId == clusterId) .OrderByDescending(n => n.ServiceLevelBase) .ThenBy(n => n.NodeId) .ToListAsync(ct); public static bool IsStale(ClusterNode node) => node.LastSeenAt is null || DateTime.UtcNow - node.LastSeenAt.Value > StaleThreshold; }