fix(adminui): wire Test Connect probes + live panels on admin-only nodes
v2-ci / build (push) Failing after 36s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped

Both bugs surfaced only on split-role deployments (the MAIN cluster's
admin-only nodes), where the AdminUI runs without the driver role.

- Test Connect returned "No probe registered" for every driver: the
  IDriverProbe set was registered only under the driver role, but the
  admin-operations singleton that consumes it is pinned to admin. Extract
  AddOtOpcUaDriverProbes() (idempotent via TryAddEnumerable) and call it
  in the hasAdmin path too.

- Live driver-status/alerts/script-log panels showed "SignalR error:
  Connection refused": these Blazor Server components opened a HubConnection
  to their own hub via the browser's public URL, which server-side code
  can't reach behind Traefik (host :9200 -> container :9000). Read the
  in-process source directly instead -- DriverStatus via
  IDriverStatusSnapshotStore.SnapshotChanged, Alerts/ScriptLog via a new
  IInProcessBroadcaster<T>. Fleet status was unaffected (reads DB/ActorSystem).

Adds unit tests for probe registration, the snapshot-store event, and the
broadcaster.
This commit is contained in:
Joseph Doherty
2026-05-29 16:38:32 -04:00
parent e3a27422a1
commit 61193629b6
14 changed files with 388 additions and 106 deletions
@@ -13,14 +13,21 @@ public static class HubServiceCollectionExtensions
public const string DriverStatusSignalRBridgeName = "driver-status-signalr-bridge";
/// <summary>
/// Registers services required by the driver-status hub pipeline:
/// <see cref="IDriverStatusSnapshotStore"/> as a singleton backed by
/// <see cref="InMemoryDriverStatusSnapshotStore"/>.
/// Registers the in-process live-push services the AdminUI's Blazor Server panels read
/// directly (instead of self-connecting a SignalR <c>HubConnection</c>, which fails behind a
/// reverse proxy — see <see cref="IInProcessBroadcaster{T}"/>):
/// <list type="bullet">
/// <item><see cref="IDriverStatusSnapshotStore"/> — last-value snapshot per driver.</item>
/// <item><see cref="IInProcessBroadcaster{T}"/> — append-stream fan-out (alarm
/// transitions, script-log lines). Registered as an open generic so each closed type
/// resolves to its own singleton shared by the bridge actor and the consuming component.</item>
/// </list>
/// </summary>
/// <param name="services">The service collection.</param>
public static IServiceCollection AddOtOpcUaDriverStatusServices(this IServiceCollection services)
{
services.AddSingleton<IDriverStatusSnapshotStore, InMemoryDriverStatusSnapshotStore>();
services.AddSingleton(typeof(IInProcessBroadcaster<>), typeof(InProcessBroadcaster<>));
return services;
}
@@ -48,11 +55,13 @@ public static class HubServiceCollectionExtensions
registry.Register<FleetStatusSignalRBridgeKey>(fleetBridge);
var alertHub = resolver.GetService<IHubContext<AlertHub>>();
var alertBridge = system.ActorOf(AlertSignalRBridge.Props(alertHub), AlertSignalRBridgeName);
var alertBroadcaster = resolver.GetService<IInProcessBroadcaster<Commons.Messages.Alerts.AlarmTransitionEvent>>();
var alertBridge = system.ActorOf(AlertSignalRBridge.Props(alertHub, alertBroadcaster), AlertSignalRBridgeName);
registry.Register<AlertSignalRBridgeKey>(alertBridge);
var scriptLogHub = resolver.GetService<IHubContext<ScriptLogHub>>();
var scriptLogBridge = system.ActorOf(ScriptLogSignalRBridge.Props(scriptLogHub), ScriptLogSignalRBridgeName);
var scriptLogBroadcaster = resolver.GetService<IInProcessBroadcaster<Commons.Messages.Logging.ScriptLogEntry>>();
var scriptLogBridge = system.ActorOf(ScriptLogSignalRBridge.Props(scriptLogHub, scriptLogBroadcaster), ScriptLogSignalRBridgeName);
registry.Register<ScriptLogSignalRBridgeKey>(scriptLogBridge);
var driverStatusHub = resolver.GetService<IHubContext<DriverStatusHub>>();