f1c188114b
#542 filed FleetStatusBroadcaster as blind to site nodes since the Phase 6 per-cluster mesh split: it derives "the fleet" from central's own Akka gossip ring, which after the split is just central's own pair. Scoping the cross-mesh rewrite found the deeper truth — the whole chain is dead end to end, so this deletes it rather than reviving it. - Zero consumers. FleetStatusBroadcaster published FleetStatusChanged on the DPS "fleet-status" topic; FleetStatusSignalRBridge forwarded it to FleetStatusHub ("/hubs/fleet-status"); nothing anywhere connected. The AdminUI bans self-connecting HubConnections, no browser JS subscribes, and /fleet (Fleet.razor) reads NodeDeploymentState from the ConfigDb plus IClusterRoleInfo on a 5 s poll instead. - Its second input never existed. DriverHostStatusHeartbeat had no production sender — only the broadcaster's own test told it — so CurrentRevision was always null and the 30 s staleness -> Degraded flip could never fire. - Post-Phase-6 DPS is pair-local, so even the central-only view it did produce was an accident of topology rather than a design. The fleet visibility that actually works is unchanged and already cross-mesh-correct: /fleet over NodeDeploymentState, and the four Phase 5 per-node telemetry dials. Precedent for deleting inert machinery rather than activating it: #522 (driver stability tiers), #507 (discovered-node injection), #525 (GetMemoryFootprint). Deleted: FleetStatusBroadcaster (+ its nested DriverHostStatusHeartbeat), its admin singleton registration and FleetStatusBroadcasterKey, FleetStatusSignalRBridge and its key, FleetStatusHub and its route mapping, and the FleetStatusChanged / FleetNodeStatus / FleetNodeHealth messages. Both Fleet/ directories survive — ControlPlane keeps ClusterNodeAddressReconciler, Commons keeps GetDiagnostics. Docs swept where they claimed a fleet-status channel exists (Telemetry.md's Phase 5 non-migrated channels, Configuration.md, CLAUDE.md); completed-program plan docs under docs/plans and docs/v2 are left as the historical record they are. Build 0 errors, 868 warnings — byte-identical to master's rebuild baseline. ControlPlane.Tests 231/231, AdminUI.Tests 1109/1109. Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
72 lines
2.9 KiB
C#
72 lines
2.9 KiB
C#
using Akka.Actor;
|
|
using Akka.Cluster.Tools.PublishSubscribe;
|
|
using Akka.Event;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Alerts;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
|
|
|
|
/// <summary>
|
|
/// Akka actor that subscribes to the <c>alerts</c> DistributedPubSub topic and forwards each
|
|
/// <see cref="AlarmTransitionEvent"/> to every SignalR client connected to <see cref="AlertHub"/>.
|
|
/// One bridge per admin node — hub fan-out is per-node, so no cluster-singleton is needed.
|
|
/// </summary>
|
|
public sealed class AlertSignalRBridge : ReceiveActor
|
|
{
|
|
public const string TopicName = "alerts";
|
|
|
|
private readonly IHubContext<AlertHub> _hub;
|
|
private readonly IInProcessBroadcaster<AlarmTransitionEvent> _broadcaster;
|
|
private readonly ILoggingAdapter _log = Context.GetLogger();
|
|
|
|
/// <summary>
|
|
/// Creates actor props for the AlertSignalRBridge.
|
|
/// </summary>
|
|
/// <param name="hub">The SignalR hub context to send alerts to.</param>
|
|
/// <param name="broadcaster">In-process fan-out read directly by the Blazor Server Alerts page.</param>
|
|
/// <returns>The Akka <see cref="Akka.Actor.Props"/> for creating this actor.</returns>
|
|
public static Props Props(IHubContext<AlertHub> hub, IInProcessBroadcaster<AlarmTransitionEvent> broadcaster) =>
|
|
Akka.Actor.Props.Create(() => new AlertSignalRBridge(hub, broadcaster));
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the AlertSignalRBridge actor.
|
|
/// </summary>
|
|
/// <param name="hub">The SignalR hub context to send alerts to.</param>
|
|
/// <param name="broadcaster">In-process fan-out read directly by the Blazor Server Alerts page.</param>
|
|
public AlertSignalRBridge(IHubContext<AlertHub> hub, IInProcessBroadcaster<AlarmTransitionEvent> broadcaster)
|
|
{
|
|
_hub = hub;
|
|
_broadcaster = broadcaster;
|
|
ReceiveAsync<AlarmTransitionEvent>(ForwardAsync);
|
|
// DPS subscription is now live — mark the feed connected so the Blazor "live" pill lights up.
|
|
Receive<SubscribeAck>(_ => _broadcaster.SetConnected(true));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void PreStart() =>
|
|
DistributedPubSub.Get(Context.System).Mediator.Tell(new Subscribe(TopicName, Self));
|
|
|
|
/// <inheritdoc />
|
|
protected override void PostStop()
|
|
{
|
|
// Bridge stopping — the feed is no longer live, drop the "live" pill.
|
|
_broadcaster.SetConnected(false);
|
|
base.PostStop();
|
|
}
|
|
|
|
private async Task ForwardAsync(AlarmTransitionEvent msg)
|
|
{
|
|
// In-process fan-out first — this is what the Blazor Server Alerts page reads. The hub push
|
|
// is kept for any out-of-process (e.g. WASM) SignalR client.
|
|
_broadcaster.Publish(msg);
|
|
try
|
|
{
|
|
await _hub.Clients.All.SendAsync(AlertHub.MethodName, msg);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_log.Warning(ex, "AlertSignalRBridge: SignalR push failed for {AlarmId}", msg.AlarmId);
|
|
}
|
|
}
|
|
}
|