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)
This commit is contained in:
126
docs/plans/2026-05-11-data-connections-treeview-refresh.md
Normal file
126
docs/plans/2026-05-11-data-connections-treeview-refresh.md
Normal file
@@ -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 `<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:
|
||||
```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 `<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:
|
||||
```razor
|
||||
<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`
|
||||
Reference in New Issue
Block a user