Files
scadalink-design/docs/plans/2026-05-11-data-connections-treeview-refresh.md
Joseph Doherty da5fdf0e63 feat(ui/admin): Topology-style refresh of Data Connections page
Brings the Data Connections admin page up to the same UX standard as the
Topology page:
- Search box with dim non-matches (opacity 0.4, shape preserved)
- Toolbar: + Connection (disabled until a site is selected), Refresh,
  Expand, Collapse
- Site context menu gains "Add Connection here" that navigates with
  ?siteId= so the form preselects + locks the Site field
- Form gains "Primary Endpoint" / "Backup Endpoint" h6 subsection
  headers matching the SiteForm convention; Failover Retry Count moved
  inside the Backup subsection
- URL renamed: /admin/connections (primary) + /admin/data-connections
  (legacy secondary @page). Same dual-route treatment on the form
- Nav label: "Data Connections" -> "Connections"
- Adds DataConnectionsPageTests bUnit suite (6 tests)
2026-05-11 22:42:48 -04:00

7.8 KiB

Data Connections page — Topology-style refresh

Date: 2026-05-11 Status: Design

Goal

Bring the Data Connections admin page up to the same UX standard as the new Topology page (/deployment/topology). The page already uses TreeView and the form already navigates as a separate page, so the refresh is a layered enhancement, not a rewrite.

Decisions (captured from Q&A)

  1. Features to add (others explicitly excluded):
    • Search with dim non-matches (opacity 0.4, shape preserved — Topology behavior)
    • Toolbar: + Connection, Refresh, Expand, Collapse
    • No per-node icons / protocol badges beyond what's already rendered
    • No selection persistence via sessionStorage (selection is in-memory only)
  2. Site context menu gains an "Add Connection here" item that navigates to the create form with ?siteId=N preselecting and locking the Site field.
  3. + Connection toolbar button is disabled until a site is selected. Selecting either a site node or one of its connection nodes resolves to that site; the create form then preselects and locks Site.
  4. No move support — moving a connection between sites is out of scope (would require a net-new service method and has knock-on effects on InstanceConnectionBinding).
  5. Empty sites still appear at the top level (so they can be right-clicked to add a connection).
  6. URL renames:
    • List page: /admin/connections (primary) + /admin/data-connections (legacy secondary).
    • Form: /admin/connections/create and /admin/connections/{Id}/edit (primary) + /admin/data-connections/create and /admin/data-connections/{Id}/edit (legacy secondaries).
    • Nav menu label changes from "Data Connections" to "Connections".
  7. Form cleanup to match the canonical SiteForm.razor style (per feedback_form_layout memory):
    • Add explicit <h6 class="text-muted border-bottom pb-1"> subsection headers: Primary Endpoint and Backup Endpoint.
    • Move Failover Retry Count inside the Backup subsection (it only applies when backup is enabled).
    • Site field stays first; read-only in edit mode; preselected & disabled when ?siteId= is passed on create.

Files to modify

src/ScadaLink.CentralUI/Components/Pages/Admin/DataConnections.razor

  • Add primary route @page "/admin/connections" and secondary legacy @page "/admin/data-connections".
  • Inject IJSRuntime only if needed (search doesn't need it; no sessionStorage).
  • Add toolbar row above the tree:
    • Search input (@bind="_searchText" @bind:event="oninput" @bind:after="OnSearchChanged")
    • btn-group with: + Connection (disabled-bind to !HasSiteSelected), Refresh, Expand, Collapse.
  • TreeView wiring:
    • Add @ref="_tree" and use _tree?.ExpandAll() / CollapseAll().
    • Set Selectable="true" and SelectedKeyChanged="OnTreeNodeSelected". Keep selected key in _selectedKey (in-memory only).
  • Search dim:
    • Recompute a HashSet<string> _matchKeys of keys whose own label or any descendant's label contains the search text.
    • In NodeContent, wrap the label <span> with style="opacity: 0.4" if a search is active and the node is not in _matchKeys.
  • Always-show-empty sites: current code already creates a Site node per Site regardless of children — keep as-is.
  • Site context menu: add an item "Add Connection here" that navigates to /admin/connections/create?siteId=@node.SiteId.
  • Connection context menu: keep Edit + Delete; update the Edit href to the new /admin/connections/{id}/edit path.

src/ScadaLink.CentralUI/Components/Pages/Admin/DataConnectionForm.razor

  • Add primary routes:
    @page "/admin/connections/create"
    @page "/admin/connections/{Id:int}/edit"
    @page "/admin/data-connections/create"
    @page "/admin/data-connections/{Id:int}/edit"
    
  • Add [SupplyParameterFromQuery] public int? SiteId { get; set; }.
  • On OnInitializedAsync, if Id is null and SiteId has a value, set _formSiteId = SiteId.Value and render the Site field as a disabled <input> (same pattern as edit mode) — also set _siteName for display.
  • Reorganize fields to subsections per SiteForm.razor reference:
    • Site (already first), Name, Protocol.
    • <h6 class="text-muted border-bottom pb-1">Primary Endpoint</h6> then Primary Endpoint Configuration.
    • <h6 class="text-muted border-bottom pb-1">Backup Endpoint</h6> — collapsed (Add Backup Endpoint button) by default; when toggled on, render: Backup Configuration, Failover Retry Count, Remove Backup button.
  • GoBack()NavigationManager.NavigateTo("/admin/connections").

src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor

  • Change <NavLink class="nav-link" href="/admin/data-connections">Data Connections</NavLink> to:
    <NavLink class="nav-link" href="/admin/connections">Connections</NavLink>
    

tests/ScadaLink.CentralUI.PlaywrightTests/NavigationTests.cs

  • Update the AdminNavLinks theory: [InlineData("Data Connections", "/admin/data-connections")][InlineData("Connections", "/admin/connections")].

New tests

tests/ScadaLink.CentralUI.Tests/DataConnectionsPageTests.cs (new)

bUnit rendering tests, modeled after TopologyPageTests:

  1. Renders_EmptyState_WhenNoSites — no sites configured.
  2. Renders_EmptySite_AsTopLevelNode — site with no connections still appears.
  3. Renders_SiteConnection_Nesting — connection nested under site after click-expand.
  4. Search_DimsNonMatches_PreservesShape — typing in search dims unmatched siblings.
  5. AddConnectionButton_DisabledUntilSiteSelected — toolbar + Connection is disabled initially, becomes enabled after clicking a site row.
  6. LegacyDataConnectionsRoute_IsDeclaredOnListPage — both /admin/connections and /admin/data-connections routes are present (reflection check).

JSInterop stubs (TreeView calls treeviewStorage.load/save even when StorageKey isn't supplied — verify):

  • JSInterop.Setup<string?>("treeviewStorage.load", _ => true).SetResult(null);
  • JSInterop.SetupVoid("treeviewStorage.save", _ => true);

Out of scope

  • Moving connections between sites (would require new service method + binding consequences).
  • Connection status indicators (live state) — DCL connection state isn't surfaced in this page; deferred.
  • Drag-and-drop reorder.
  • Selection persistence across page reloads.

Verification

  1. dotnet build clean.
  2. dotnet test tests/ScadaLink.CentralUI.Tests/ScadaLink.CentralUI.Tests.csproj — all green incl. new tests.
  3. Existing Playwright NavigationTests pass with the updated label/URL.
  4. Browser smoke (after bash docker/deploy.sh):
    • /admin/data-connections (legacy bookmark) loads the same page as /admin/connections.
      • Connection disabled until a site is selected; then navigates with ?siteId=N; Site field is locked in the form.
    • Right-click on an empty site → "Add Connection here" works.
    • Search "OPC" dims non-matching connections (label-based search, case-insensitive).
    • Expand / Collapse buttons work; Refresh re-fetches from repos.
    • Form sections "Primary Endpoint" / "Backup Endpoint" render with the SiteForm-style headers; Failover Retry Count appears inside the Backup section only when backup is enabled.

Critical files

  • src/ScadaLink.CentralUI/Components/Pages/Admin/DataConnections.razor
  • src/ScadaLink.CentralUI/Components/Pages/Admin/DataConnectionForm.razor
  • src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor
  • tests/ScadaLink.CentralUI.PlaywrightTests/NavigationTests.cs
  • tests/ScadaLink.CentralUI.Tests/DataConnectionsPageTests.cs (new)

Reference patterns

  • TreeView usage with toolbar/search: src/ScadaLink.CentralUI/Components/Pages/Deployment/Topology.razor
  • Form layout convention: src/ScadaLink.CentralUI/Components/Pages/Admin/SiteForm.razor
  • bUnit harness for tree page: tests/ScadaLink.CentralUI.Tests/TopologyPageTests.cs