feat(centralui+dcl): Test Bindings popup — one-shot live read of bound tags
Adds a Test Bindings button to the Connection Bindings table on the Configure
Instance page that opens a modal showing the live current value of every bound
attribute. Reuses the routing path that the OPC UA tag browser landed on:
Central: TestBindingsDialog → IBindingTester → CommunicationService
→ ReadTagValuesCommand → SiteEnvelope (Ask)
Site: SiteCommunicationActor → DeploymentManagerActor singleton
→ DataConnectionManagerActor → child DataConnectionActor
→ _adapter.ReadBatchAsync
Split mirrors the browse handler:
• Manager owns ConnectionNotFound (only it sees the per-site connection set).
• Child owns ConnectionNotConnected (pre-call status check, never stash —
read is interactive design-time), Timeout (OperationCanceledException),
ServerError (any other exception). Per-tag failures from ReadBatchAsync
become failure TagReadOutcomes without aborting the batch.
CentralUI:
• IBindingTester / BindingTester — Design-role guard via HasClaim against
JwtTokenService.RoleClaimType (not IsInRole — see c1e16cf), typed
transport-failure translation.
• TestBindingsDialog — ShowAsync(siteId, rows, instanceLabel) method-arg
pattern (no Razor parameter race; see 2c138b6), groups rows by connection
and issues one ReadAsync per connection in parallel, per-row error subline
+ per-connection banner, Refresh button re-issues the reads.
• InstanceConfigure.razor — Test Bindings button next to Save Bindings,
disabled when no testable rows. OPC UA only today (other protocols have
no ReadTagValuesCommand wiring yet).
Tests:
• Commons: ReadTagValuesCommand discovered by ManagementCommandRegistry.
• DataConnectionLayer: unknown connection → ConnectionNotFound,
not-connected adapter → ConnectionNotConnected (ReadBatchAsync NOT called),
success-path mapping (Good/Bad + per-tag error), cancellation → Timeout.
• CentralUI: register IBindingTester (and the previously-missing
IOpcUaBrowseService) on the existing InstanceConfigureAuditDrillinTests
Bunit container so the page renders cleanly with the new dialog.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
|
||||
|
||||
/// <summary>
|
||||
/// Sent from CentralUI to a specific site to read the current value of one or
|
||||
/// more tags on the live server backing a named data connection. Backs the
|
||||
/// "Test Bindings" dialog on the Configure Instance page — a one-shot read of
|
||||
/// every bound attribute, grouped by connection, with no subscription.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Keyed by <see cref="ConnectionName"/> (not id) for the same reason as
|
||||
/// <see cref="BrowseOpcUaNodeCommand"/>: the site-side
|
||||
/// <c>DataConnectionManagerActor</c> indexes its children by connection name,
|
||||
/// and the central UI already has the connection name in scope from the
|
||||
/// bindings table. The central <c>DataConnections</c> table's id is not
|
||||
/// exposed at the site.
|
||||
/// </remarks>
|
||||
/// <param name="ConnectionName">Name of the site-local data connection to read against.</param>
|
||||
/// <param name="TagPaths">Tag paths to read (one batch per connection — caller groups by connection name).</param>
|
||||
public record ReadTagValuesCommand(
|
||||
string ConnectionName,
|
||||
IReadOnlyList<string> TagPaths);
|
||||
|
||||
/// <summary>
|
||||
/// Per-tag outcome of a <see cref="ReadTagValuesCommand"/>. The site returns
|
||||
/// one outcome per requested tag path; a single failing tag never aborts the
|
||||
/// batch (the underlying <c>IDataConnection.ReadBatchAsync</c> contract).
|
||||
/// </summary>
|
||||
/// <param name="TagPath">Tag path that was read — matches an entry in the request.</param>
|
||||
/// <param name="Success">True when the read returned a value; false when the per-tag read failed.</param>
|
||||
/// <param name="Value">Read value (may be null even on success); always null on failure.</param>
|
||||
/// <param name="Quality">Quality code as a string (<c>Good</c>/<c>Bad</c>/<c>Uncertain</c>); always <c>Bad</c> on failure.</param>
|
||||
/// <param name="Timestamp">Source timestamp on success; the central-noted UTC time of the failure otherwise.</param>
|
||||
/// <param name="ErrorMessage">Per-tag error message on failure; null on success.</param>
|
||||
public record TagReadOutcome(
|
||||
string TagPath,
|
||||
bool Success,
|
||||
object? Value,
|
||||
string Quality,
|
||||
DateTimeOffset Timestamp,
|
||||
string? ErrorMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Reply to a <see cref="ReadTagValuesCommand"/>. Either <see cref="Outcomes"/>
|
||||
/// is populated (one entry per requested tag, in any order) and
|
||||
/// <see cref="Failure"/> is null, or <see cref="Failure"/> is set and
|
||||
/// <see cref="Outcomes"/> is empty — the latter is the connection-level
|
||||
/// short-circuit (unknown connection, not connected, server error, etc.) where
|
||||
/// no per-tag attempt was made.
|
||||
/// </summary>
|
||||
public record ReadTagValuesResult(
|
||||
IReadOnlyList<TagReadOutcome> Outcomes,
|
||||
ReadTagValuesFailure? Failure);
|
||||
|
||||
/// <summary>
|
||||
/// Connection-level failure carried by <see cref="ReadTagValuesResult"/>. The
|
||||
/// dialog maps each <see cref="ReadTagValuesFailureKind"/> to a friendly
|
||||
/// banner; <see cref="Message"/> is surfaced verbatim for the
|
||||
/// <see cref="ReadTagValuesFailureKind.ServerError"/> case.
|
||||
/// </summary>
|
||||
public record ReadTagValuesFailure(
|
||||
ReadTagValuesFailureKind Kind,
|
||||
string Message);
|
||||
|
||||
public enum ReadTagValuesFailureKind
|
||||
{
|
||||
ConnectionNotFound,
|
||||
ConnectionNotConnected,
|
||||
Timeout,
|
||||
ServerError
|
||||
}
|
||||
Reference in New Issue
Block a user