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:
@@ -47,6 +47,7 @@ public class DataConnectionManagerActor : ReceiveActor
|
||||
Receive<RemoveConnectionCommand>(HandleRemoveConnection);
|
||||
Receive<GetAllHealthReports>(HandleGetAllHealthReports);
|
||||
Receive<BrowseOpcUaNodeCommand>(HandleBrowse);
|
||||
Receive<ReadTagValuesCommand>(HandleReadTagValues);
|
||||
}
|
||||
|
||||
private void HandleCreateConnection(CreateConnectionCommand command)
|
||||
@@ -140,6 +141,33 @@ public class DataConnectionManagerActor : ReceiveActor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Routes a <see cref="ReadTagValuesCommand"/> from the CentralUI's Test
|
||||
/// Bindings dialog to the child <see cref="DataConnectionActor"/> that
|
||||
/// owns the named connection. Same split as <see cref="HandleBrowse"/> —
|
||||
/// the manager owns
|
||||
/// <see cref="ReadTagValuesFailureKind.ConnectionNotFound"/> because it is
|
||||
/// the only actor with site-level visibility; every other failure
|
||||
/// (not connected, server error, timeout) is resolved by the child where
|
||||
/// the adapter is held.
|
||||
/// </summary>
|
||||
private void HandleReadTagValues(ReadTagValuesCommand command)
|
||||
{
|
||||
if (_connectionActors.TryGetValue(command.ConnectionName, out var actor))
|
||||
{
|
||||
actor.Forward(command);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Warning("No connection actor for {0} during test-bindings read", command.ConnectionName);
|
||||
Sender.Tell(new ReadTagValuesResult(
|
||||
Array.Empty<TagReadOutcome>(),
|
||||
new ReadTagValuesFailure(
|
||||
ReadTagValuesFailureKind.ConnectionNotFound,
|
||||
$"No data connection named '{command.ConnectionName}' at this site.")));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleRemoveConnection(RemoveConnectionCommand command)
|
||||
{
|
||||
if (_connectionActors.TryGetValue(command.ConnectionName, out var actor))
|
||||
|
||||
Reference in New Issue
Block a user