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:
+62
-1
@@ -154,8 +154,18 @@
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="p-2">
|
||||
<div class="p-2 d-flex gap-2">
|
||||
<button class="btn btn-success btn-sm" @onclick="SaveBindings" disabled="@_saving">Save Bindings</button>
|
||||
@* Test Bindings: one-shot live read of every bound attribute
|
||||
whose row has a connection picked AND an effective tag
|
||||
path. Disabled when no testable rows. Currently OPC UA
|
||||
only — other protocols (none yet) would need their own
|
||||
wire+adapter support to round-trip through ReadTagValuesCommand. *@
|
||||
<button class="btn btn-outline-primary btn-sm"
|
||||
@onclick="OpenTestBindings"
|
||||
disabled="@(!HasTestableBindings())">
|
||||
Test Bindings
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -362,6 +372,11 @@
|
||||
ConnectionName="@_browserConnectionName"
|
||||
InitialNodeId="@_browserInitial"
|
||||
OnSelected="OnBrowserSelected" />
|
||||
|
||||
@* Test Bindings dialog — one-shot live read of every bound attribute.
|
||||
Method-arg ShowAsync(siteId, rows) — no Razor parameter propagation
|
||||
race (same pattern as OpcUaBrowserDialog). *@
|
||||
<TestBindingsDialog @ref="_testBindingsRef" />
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -399,6 +414,10 @@
|
||||
private string? _browserInitial;
|
||||
private string _siteIdentifier = "";
|
||||
|
||||
// Test Bindings dialog — single instance, args passed via ShowAsync (no
|
||||
// Razor parameter propagation race; same pattern as the OPC UA browser).
|
||||
private TestBindingsDialog? _testBindingsRef;
|
||||
|
||||
// Overrides
|
||||
private List<TemplateAttribute> _overrideAttrs = new();
|
||||
private Dictionary<string, string?> _overrideValues = new();
|
||||
@@ -583,6 +602,48 @@
|
||||
_browserAttrInEdit = null;
|
||||
}
|
||||
|
||||
// ── Test Bindings (one-shot live read of bound tags) ────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Builds the list of testable rows: attributes that have a connection
|
||||
/// picked AND a non-empty effective tag path AND an OPC UA connection
|
||||
/// (the only protocol routed through <c>ReadTagValuesCommand</c> today).
|
||||
/// </summary>
|
||||
private List<TestBindingsDialog.BindingRowToTest> BuildTestableRows()
|
||||
{
|
||||
var rows = new List<TestBindingsDialog.BindingRowToTest>();
|
||||
foreach (var attr in _bindingDataSourceAttrs)
|
||||
{
|
||||
var connId = GetBindingConnectionId(attr.Name);
|
||||
if (connId <= 0) continue;
|
||||
|
||||
var conn = _siteConnections.FirstOrDefault(c => c.Id == connId);
|
||||
if (conn is null) continue;
|
||||
|
||||
// OPC UA only — other protocols don't have a site-side
|
||||
// ReadTagValuesCommand handler wired up yet.
|
||||
if (!string.Equals(conn.Protocol, "OpcUa", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var effectivePath = _bindingOverrides.GetValueOrDefault(attr.Name)
|
||||
?? GetTemplateDefault(attr.Name);
|
||||
if (string.IsNullOrWhiteSpace(effectivePath)) continue;
|
||||
|
||||
rows.Add(new TestBindingsDialog.BindingRowToTest(attr.Name, conn.Name, effectivePath));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
private bool HasTestableBindings() => BuildTestableRows().Count > 0;
|
||||
|
||||
private async Task OpenTestBindings()
|
||||
{
|
||||
if (_testBindingsRef is null) return;
|
||||
var rows = BuildTestableRows();
|
||||
if (rows.Count == 0) return;
|
||||
await _testBindingsRef.ShowAsync(_siteIdentifier, rows, _instance?.UniqueName ?? "");
|
||||
}
|
||||
|
||||
private async Task SaveBindings()
|
||||
{
|
||||
_saving = true;
|
||||
|
||||
Reference in New Issue
Block a user