61193629b6
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.
81 lines
4.3 KiB
C#
81 lines
4.3 KiB
C#
using Akka.Actor;
|
|
using Akka.Hosting;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
|
|
|
|
public static class HubServiceCollectionExtensions
|
|
{
|
|
public const string FleetStatusSignalRBridgeName = "fleet-status-signalr-bridge";
|
|
public const string AlertSignalRBridgeName = "alert-signalr-bridge";
|
|
public const string ScriptLogSignalRBridgeName = "script-log-signalr-bridge";
|
|
public const string DriverStatusSignalRBridgeName = "driver-status-signalr-bridge";
|
|
|
|
/// <summary>
|
|
/// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Spawns the SignalR bridge actors that forward DPS messages to browser-facing SignalR
|
|
/// hubs: <c>fleet-status</c> → <see cref="FleetStatusHub"/>, <c>alerts</c> →
|
|
/// <see cref="AlertHub"/>, <c>script-logs</c> → <see cref="ScriptLogHub"/>.
|
|
///
|
|
/// Call inside the admin-role configurator on the shared <see cref="AkkaConfigurationBuilder"/>:
|
|
/// <code>
|
|
/// if (hasAdmin)
|
|
/// {
|
|
/// ab.WithOtOpcUaControlPlaneSingletons();
|
|
/// ab.WithOtOpcUaSignalRBridges();
|
|
/// }
|
|
/// </code>
|
|
/// </summary>
|
|
/// <param name="builder">The Akka configuration builder.</param>
|
|
public static AkkaConfigurationBuilder WithOtOpcUaSignalRBridges(this AkkaConfigurationBuilder builder)
|
|
{
|
|
builder.WithActors((system, registry, resolver) =>
|
|
{
|
|
var fleetHub = resolver.GetService<IHubContext<FleetStatusHub>>();
|
|
var fleetBridge = system.ActorOf(FleetStatusSignalRBridge.Props(fleetHub), FleetStatusSignalRBridgeName);
|
|
registry.Register<FleetStatusSignalRBridgeKey>(fleetBridge);
|
|
|
|
var alertHub = resolver.GetService<IHubContext<AlertHub>>();
|
|
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 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>>();
|
|
var driverStatusStore = resolver.GetService<IDriverStatusSnapshotStore>();
|
|
var driverStatusBridge = system.ActorOf(DriverStatusSignalRBridge.Props(driverStatusHub, driverStatusStore), DriverStatusSignalRBridgeName);
|
|
registry.Register<DriverStatusSignalRBridgeKey>(driverStatusBridge);
|
|
});
|
|
return builder;
|
|
}
|
|
}
|
|
|
|
/// <summary>Marker keys for <see cref="ActorRegistry"/> lookup of the SignalR bridge actors.</summary>
|
|
public sealed class FleetStatusSignalRBridgeKey { }
|
|
public sealed class AlertSignalRBridgeKey { }
|
|
public sealed class ScriptLogSignalRBridgeKey { }
|
|
public sealed class DriverStatusSignalRBridgeKey { }
|