From 0bbe63737016e9a3db18820340d388c004ead371 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 27 Jun 2026 12:04:12 -0400 Subject: [PATCH] =?UTF-8?q?docs(secured-writes):=20design=20=E2=80=94=20ta?= =?UTF-8?q?g=20selector=20+=20data-type=20auto-fill=20from=20MxGateway=20b?= =?UTF-8?q?rowse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...6-27-secured-writes-tag-selector-design.md | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 docs/plans/2026-06-27-secured-writes-tag-selector-design.md diff --git a/docs/plans/2026-06-27-secured-writes-tag-selector-design.md b/docs/plans/2026-06-27-secured-writes-tag-selector-design.md new file mode 100644 index 00000000..c918f739 --- /dev/null +++ b/docs/plans/2026-06-27-secured-writes-tag-selector-design.md @@ -0,0 +1,171 @@ +# Secured Writes — Tag Selector + Data-Type Auto-Fill (Design) + +**Date:** 2026-06-27 +**Component(s):** Central UI (#9), Data Connection Layer (#4); Security & Auth (#10) for the Secured Writes UX. +**Status:** Approved design — ready for implementation plan. + +## Problem + +On the Central UI Secured Writes page (`/operations/secured-writes`), an Operator submits a +two-person MxGateway secured write. Today the **Tag path** is a free-text input and the **Data +type** is a manually-chosen dropdown defaulting to `Boolean`. Operators must know the exact +MxGateway tag reference and the correct data type by hand — error-prone, and a wrong data type +only surfaces at *approval* time (the value can't be decoded/written). + +We want: +1. The **Tag path** field to be backed by a **tag selector** (browse the connection's tags). +2. The **Data type** to be **auto-selected from the chosen tag**. + +## Key insight — the data type is already available + +Secured writes are **MxGateway-only** (the page filters connections to `Protocol == "MxGateway"`, +and the ManagementActor enforces the same on submit). The MxGateway gateway *already returns* a +per-attribute `data_type_name` (free-form Galaxy text, e.g. `"Float"`, `"Integer"`, `"Boolean"`) +on its browse response (`GalaxyAttribute.data_type_name` in `galaxy_repository.proto`). ScadaBridge +currently **discards** it: `RealMxGatewayClient.BrowseChildrenAsync` reads only +`FullTagReference` + `AttributeName` when building `MxBrowseChild`. + +So the feature is mostly *plumbing an existing value through* + reusing the existing +`NodeBrowserDialog`. No new gRPC, no DB/schema/migration, no new cross-cluster message contracts. + +Reused as-is: +- `NodeBrowserDialog` / `TreeRow` tag browser (already used by `InstanceConfigure`, + `TestBindingsDialog`). +- The full browse call path: `IBrowseService` → `CommunicationService.BrowseNodeAsync` → + `SiteCommunicationActor` → `DeploymentManager` → `DataConnectionManagerActor` → + `DataConnectionActor` → adapter. +- `BrowseNode` already carries an optional `DataType` field. + +## UX decisions (confirmed) + +- **Tag field:** editable text field **+ a "Browse…" button** (hybrid, matching + `InstanceConfigure`). Operators can still paste a known reference; the browser populates the field. +- **Data type:** **auto-filled from the tag but left editable**. The Galaxy type name is free-form; + the map to ScadaBridge's `DataType` enum is best-effort, so an unmapped type must leave the + operator able to choose. + +## Design + +### Layer 1 — Surface `DataType` from MxGateway browse (DCL) + +- `Adapters/IMxGatewayClient.cs`: extend the seam record additively — + `record MxBrowseChild(string NodeId, string DisplayName, BrowseNodeClass NodeClass, bool HasChildren, string? DataType = null)`. +- `Adapters/RealMxGatewayClient.cs` (`BrowseChildrenAsync`): when projecting + `parentObject.Attributes`, pass `attr.DataTypeName` (normalise empty → `null`) into `MxBrowseChild`. +- `Adapters/MxGatewayDataConnection.cs`: forward it — + `new BrowseNode(c.NodeId, c.DisplayName, c.NodeClass, c.HasChildren, DataType: c.DataType)`. +- Test fake `FakeMxGatewayClient`: update to the new record shape and emit a sample `DataType` so + tests can assert end-to-end surfacing. + +`ValueRank`/`Writable` remain `null` for MxGateway (not needed here). Array attributes +(`is_array`/`array_dimension`) are **explicitly out of scope** — a secured write to an array via +this form is an edge case; the operator can pick `List` manually. + +### Layer 2 — Let the browser emit the full node (shared dialog) + +`NodeBrowserDialog` currently emits only `OnSelected(string nodeId)`. Add, additively: +- `EventCallback OnNodeSelected` — invoked alongside the existing `OnSelected` in + `SelectBrowseNode()`. The internal tree node retains its source `BrowseNode` so the emit is + faithful (NodeId + DataType). Existing callers are unaffected (the callback is optional). +- `bool ShowSearch = true` — set `false` from Secured Writes. MxGateway does **not** implement + `IAddressSpaceSearchable`, so the search box would always return a `NotBrowsable` failure; + hiding it avoids a guaranteed-broken affordance. Default `true` keeps OPC UA callers unchanged. + +### Layer 3 — Wire the form (`Components/Pages/Operations/SecuredWrites.razor`) + +- Tag path becomes an input-group: the existing editable `` + a `Browse…` button, + `disabled` unless a site **and** an MxGateway connection are selected + (`CanBrowse`, mirroring `InstanceConfigure`'s `canBrowse`). +- Render one ``. +- `OpenBrowser()` → `await _browserRef.ShowAsync(_formSiteIdentifier, _formConnectionName, _formTagPath)` + (seeds with whatever is already typed). +- `OnTagPicked(BrowseNode node)`: + ```csharp + _formTagPath = node.NodeId; + if (SecuredWriteDataTypeMapper.TryMap(node.DataType, out var dt)) + _formValueType = dt.ToString(); + // else: leave _formValueType unchanged — operator picks + ``` +- The Data type `