feat(adminui): B2-WP6 browse re-target — /raw device browse commits raw tags

Browse device… on a /raw Device/TagGroup opens the discovery-browser modal
against the merged Driver+Device config and commits selected browse leaves as
raw Tag rows under the target, via IRawTreeService.ImportTagsAsync.

- RawBrowseModal.razor: two-tier browse gate (bespoke IDriverBrowser →
  universal DiscoveryDriverBrowser via SupportsOnlineDiscovery → disabled),
  merged config from LoadMergedProbeConfigAsync, multi-select over
  DriverBrowseTree, opt-in 'create matching tag-groups' folder mirror,
  commit via ImportTagsAsync.
- RawBrowseCommitMapper: pure leaf→RawTagImportRow mapper — DriverDataType→
  OPC UA type, per-driver address field (nodeId/tagPath/symbolPath/address/
  attributeRef), target-group + mirror path combine. Unit-tested (39 tests).
- DriverBrowseTree: additive multi-select mode (checkboxes + ancestor-path
  callback), default off preserves single-select pickers.
- RawTree: OnBrowseDevice wired to the modal (Device + TagGroup menus).

Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
This commit is contained in:
Joseph Doherty
2026-07-16 04:14:27 -04:00
parent ae9f906f52
commit 1f43449942
6 changed files with 774 additions and 8 deletions
@@ -44,6 +44,9 @@
<RawCsvExportModal @bind-Visible="_csvExportVisible" DeviceId="@_csvExportDeviceId"
TagGroupId="@_csvExportTagGroupId" DriverType="@_csvExportDriverType" />
<RawBrowseModal @bind-Visible="_browseVisible" DeviceId="@_browseDeviceId"
TagGroupId="@_browseTagGroupId" DriverType="@_browseDriverType" OnSaved="BrowseSaved" />
<RawNameDialog @bind-Visible="_nameVisible" Title="@_nameTitle" Value="@_nameInitial" OnSubmit="NameSubmitted" />
<RawConfirmDialog @bind-Visible="_confirmVisible" Title="@_confirmTitle" Message="@_confirmMessage"
@@ -105,6 +108,13 @@
private string? _csvExportTagGroupId;
private string _csvExportDriverType = "";
// --- Browse-device modal (WP6 discovery-browser re-target) ---
private bool _browseVisible;
private string _browseDeviceId = "";
private string? _browseTagGroupId;
private string _browseDriverType = "";
private RawNode? _browseNode;
// --- Name dialog (New folder/group, Rename) ---
private bool _nameVisible;
private string _nameTitle = "";
@@ -511,10 +521,16 @@
return Task.CompletedTask;
}
// Browse device stays a placeholder — Wave C (WP6) wires the discovery-browser modal.
// Opens the discovery-browser modal against the target device/group. On a Device node the browse commits
// at the device root; on a TagGroup node the group's device-relative path is prepended to every tag.
private Task OnBrowseDevice(RawNode node)
{
ShowMessage("Browse device", "Device browse arrives in the browse wave (Wave C).", node.DisplayName);
_browseDeviceId = FindAncestorDevice(node)?.EntityId ?? "";
_browseTagGroupId = node.Kind == RawNodeKind.TagGroup ? node.EntityId : null;
_browseDriverType = node.DriverType ?? "";
_browseNode = node;
_browseVisible = true;
StateHasChanged();
return Task.CompletedTask;
}
@@ -598,6 +614,12 @@
if (_csvImportNode is not null) { await ReloadNodeAsync(_csvImportNode); }
}
private async Task BrowseSaved()
{
// New tags (and any mirrored groups) land under the browsed node — reload its children.
if (_browseNode is not null) { await ReloadNodeAsync(_browseNode); }
}
// ---------------------------------------------------------------------------
// Rename / delete outcome surfacing.
// ---------------------------------------------------------------------------