diff --git a/docs/plans/2026-05-11-data-connections-treeview-refresh.md b/docs/plans/2026-05-11-data-connections-treeview-refresh.md new file mode 100644 index 0000000..d1a25ef --- /dev/null +++ b/docs/plans/2026-05-11-data-connections-treeview-refresh.md @@ -0,0 +1,126 @@ +# 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 `
` 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 _matchKeys` of keys whose own label or any descendant's label contains the search text. + - In `NodeContent`, wrap the label `` 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: + ```razor + @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 `` (same pattern as edit mode) — also set `_siteName` for display. +- Reorganize fields to subsections per `SiteForm.razor` reference: + - Site (already first), Name, Protocol. + - `
Primary Endpoint
` then Primary Endpoint Configuration. + - `
Backup Endpoint
` — 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 `Data Connections` to: + ```razor + Connections + ``` + +### `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("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` diff --git a/src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor b/src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor index 5bceae1..59c4fc9 100644 --- a/src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor +++ b/src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor @@ -21,7 +21,7 @@ Sites