using Mbproxy.Proxy; using Microsoft.AspNetCore.SignalR; namespace Mbproxy.Admin; /// /// SignalR hub backing the live admin dashboard. Two subscription scopes: /// /// — the fleet dashboard (GET /) joins the /// group and receives a "fleet" message every /// push tick. /// — a connection-detail page (GET /plc/{name}) /// joins and receives a "plc" message. The first /// subscriber to a PLC arms that PLC's tag-value capture; the last to leave /// disarms it (on-demand capture). /// /// /// The hub itself is transient (one instance per call). Cross-call state — the /// subscriber counts that drive capture arming — lives in the singleton /// . The actual pushes are issued by /// , not the hub. /// internal sealed class StatusHub : Hub { /// SignalR group name for fleet-dashboard subscribers. public const string FleetGroup = "fleet"; /// SignalR group name for a single PLC's detail-page subscribers. public static string PlcGroup(string plcName) => "plc:" + plcName; private readonly PlcSubscriptionTracker _tracker; private readonly TagCaptureRegistry _captureRegistry; public StatusHub(PlcSubscriptionTracker tracker, TagCaptureRegistry captureRegistry) { _tracker = tracker; _captureRegistry = captureRegistry; } /// Subscribes the calling connection to fleet-wide status pushes. public Task SubscribeFleet() => Groups.AddToGroupAsync(Context.ConnectionId, FleetGroup); /// /// Subscribes the calling connection to one PLC's detail pushes and arms that PLC's /// tag-value capture if this is its first viewer. /// public async Task SubscribePlc(string plcName) { await Groups.AddToGroupAsync(Context.ConnectionId, PlcGroup(plcName)).ConfigureAwait(false); if (_tracker.Add(Context.ConnectionId, plcName)) _captureRegistry.Arm(plcName); // no-op for an unknown PLC name } /// /// On disconnect, drops every subscription the connection held and disarms the /// capture of any PLC whose last viewer just left. /// public override Task OnDisconnectedAsync(Exception? exception) { foreach (var plcName in _tracker.RemoveConnection(Context.ConnectionId)) _captureRegistry.Disarm(plcName); return base.OnDisconnectedAsync(exception); } }