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
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:
@@ -0,0 +1,41 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
|
||||
|
||||
/// <summary>
|
||||
/// A singleton, in-process fan-out for live event streams (alarm transitions, script-log
|
||||
/// lines). A per-node SignalR bridge actor subscribes to the cluster's DistributedPubSub topic
|
||||
/// and calls <see cref="Publish"/>; Blazor Server components subscribe to <see cref="Received"/>
|
||||
/// to render the live tail.
|
||||
/// <para>
|
||||
/// This exists because the AdminUI runs as Blazor <em>Server</em>: a component opening a
|
||||
/// SignalR <c>HubConnection</c> to its own hub would dial the browser's public URL from
|
||||
/// server-side code, which is unreachable behind a reverse proxy (e.g. Traefik mapping host
|
||||
/// :9200 → container :9000) and so fails with "Connection refused". Reading this in-process
|
||||
/// broadcaster instead avoids the network hop entirely. Mirrors the
|
||||
/// <c>IDriverStatusSnapshotStore.SnapshotChanged</c> pattern for stream (vs. last-value) feeds.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The event payload type (e.g. AlarmTransitionEvent, ScriptLogEntry).</typeparam>
|
||||
public interface IInProcessBroadcaster<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised once per <see cref="Publish"/> with the published item. Handlers run on the
|
||||
/// caller's thread (the bridge actor), so subscribers must marshal to their own sync
|
||||
/// context (Blazor's <c>InvokeAsync</c>).
|
||||
/// </summary>
|
||||
event Action<T>? Received;
|
||||
|
||||
/// <summary>Fan the item out to all current <see cref="Received"/> subscribers.</summary>
|
||||
void Publish(T item);
|
||||
}
|
||||
|
||||
/// <summary>Thread-safe singleton implementation of <see cref="IInProcessBroadcaster{T}"/>.</summary>
|
||||
/// <typeparam name="T">The event payload type.</typeparam>
|
||||
public sealed class InProcessBroadcaster<T> : IInProcessBroadcaster<T>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public event Action<T>? Received;
|
||||
|
||||
/// <inheritdoc />
|
||||
// Capture-then-invoke (via ?.) so a concurrent unsubscribe can't null the delegate mid-raise.
|
||||
public void Publish(T item) => Received?.Invoke(item);
|
||||
}
|
||||
Reference in New Issue
Block a user