feat(ui/deployment): consolidate sites/areas/instances into Topology page
Single /deployment/topology page replaces /deployment/instances (legacy URL preserved as a secondary @page directive) and the /admin/areas* CRUD pages. TreeView with Site → Area → Instance, V1–V7 visual guide (bi-building / bi-diagram-3 / bi-box), always-visible empty containers, search dim, F2 inline area rename, and right-click context menus per node kind (Add Area, Move to Area…, lifecycle actions, etc.). Adds AreaService.MoveAreaAsync with cycle prevention, same-site enforcement, and name-collision check at the new parent. Instance rename intentionally out of scope — UniqueName is the site-side actor identity, requires its own design pass.
This commit is contained in:
@@ -704,34 +704,52 @@ record TestNode(string Key, string Label, List<TestNode> Children);
|
||||
|
||||
## Page Integration Notes
|
||||
|
||||
### 1. Instances Page (`/deployment/instances` — Instances.razor)
|
||||
### 1. Topology Page (`/deployment/topology` — Topology.razor)
|
||||
|
||||
**Current state:** Flat table with filters (Site, Template, Status, Search), pagination, and 6 inline action buttons per row (Deploy, Disable/Enable, Configure, Diff, Delete). ~490 lines.
|
||||
The Topology page is the single home for Site → Area → Instance hierarchy management. It replaces the former `/deployment/instances` page (the legacy URL is retained as a secondary `@page` directive on `Topology.razor` so existing bookmarks resolve) and the former `/admin/areas*` admin pages.
|
||||
|
||||
**Change to:**
|
||||
- Replace the `<table>` with a `<TreeView>` showing Site → Area → Sub Area → Instance hierarchy.
|
||||
- **Keep the existing filter bar** (Site, Template, Status, Search). Filters control which tree roots and leaves are shown:
|
||||
- Site filter: pass only the matching site root to `Items`.
|
||||
- Template/Status/Search filters: filter at the instance (leaf) level. Branch nodes with no matching descendants should be pruned from the tree. Build a helper method (`BuildFilteredTree()`) that walks the hierarchy bottom-up, keeping only branches that contain at least one matching instance.
|
||||
- **Remove the table, pagination, and Actions column.** The tree replaces all of this.
|
||||
- **Move all 6 action buttons into the `ContextMenu` fragment**, shown only for instance nodes:
|
||||
- Deploy/Redeploy, Disable/Enable (conditional on state), Configure, Diff, Delete (with divider).
|
||||
- Site and Area nodes get no context menu (browser default).
|
||||
- **Node content per type:**
|
||||
- Site nodes: `<span class="fw-semibold">SiteName</span>`
|
||||
- Area nodes: `<span class="text-secondary">AreaName</span>`
|
||||
- Instance nodes: `<span>UniqueName</span>` + status badge + staleness badge
|
||||
- **Tree model:** Build in `LoadDataAsync` — load sites, areas (recursive via `ParentAreaId`), instances. Group instances by `SiteId` + `AreaId`. Instances with `AreaId == null` attach directly under their site. Wrap in a uniform `TreeNode` record.
|
||||
- **StorageKey:** `"instances-tree"`
|
||||
- **Selection:** Enable selection. Clicking an instance could show a detail panel or simply highlight it for context menu use.
|
||||
**Scope:**
|
||||
- Structural management of areas (create, rename inline, move, delete) and instance placement (move to area).
|
||||
- Instance lifecycle: Deploy/Redeploy, Enable/Disable, Configure, Diff, Delete via per-node context menu.
|
||||
- Search-only filter row (single text input) — dims non-matching rows, preserves tree shape, no collapse.
|
||||
|
||||
**Files to modify:**
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/Instances.razor` — replace table with TreeView, add tree model building, move actions to context menu, keep filter bar.
|
||||
**TreeView wiring:**
|
||||
- `Items` = list of Site root nodes built from `_sites`, `_allAreas`, and `_allInstances`.
|
||||
- `KeySelector` returns prefixed keys (`s:{id}`, `a:{id}`, `i:{id}`).
|
||||
- `StorageKey` = `"topology-tree"` for expansion state.
|
||||
- A separate `topology-tree-selected` sessionStorage key persists the selected node across navigation.
|
||||
- `Selectable` = true; selection does not navigate (instance configure goes through the context menu).
|
||||
- Empty containers always rendered (so they can be drop/move targets).
|
||||
|
||||
**Removed code:**
|
||||
- Pagination logic (`_currentPage`, `_totalPages`, `_pagedInstances`, `GoToPage`)
|
||||
- Actions column markup
|
||||
- `<table>` / `<thead>` / `<tbody>` structure
|
||||
**Glyphs (V1–V7 visual guide):**
|
||||
- Site: `bi-building`
|
||||
- Area: `bi-diagram-3`
|
||||
- Instance: `bi-box` + state badge + Stale/Current badge when deployed.
|
||||
|
||||
**Context menus:**
|
||||
- **Site:** Add Area, Create Instance here.
|
||||
- **Area:** Add Sub-area, Create Instance here, Move to Area…, Rename… (also F2 / double-click inline), Delete.
|
||||
- **Instance:** Deploy/Redeploy, Enable/Disable (state-dependent), Configure, Diff, Move to Area…, Delete. Instance rename is intentionally absent (see "Instance rename" below).
|
||||
|
||||
**Inline rename:** Area rows only. F2 or double-click swaps the label for an input bound to a local buffer. Enter commits via `AreaService.UpdateAreaAsync`; Escape cancels; server validation errors stay surfaced inline.
|
||||
|
||||
**Search behavior:** Single text input above the tree. While text is present, any row whose label does not match (case-insensitive substring) and whose subtree contains no match is rendered at `opacity: 0.4`. The tree shape stays intact.
|
||||
|
||||
**Top-of-page buttons:** `+ Area` (opens `CreateAreaDialog` with site picker), `+ Instance` (navigates to `/deployment/instances/create` with no preselection), `Refresh`, `Expand`, `Collapse`.
|
||||
|
||||
**Files added:**
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/Topology.razor`
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/MoveAreaDialog.razor`
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/MoveInstanceDialog.razor`
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/CreateAreaDialog.razor`
|
||||
|
||||
**Files removed:**
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Deployment/Instances.razor`
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Admin/Areas.razor` (and AreaAdd / AreaEdit / AreaDelete)
|
||||
|
||||
**Backend addition:** `AreaService.MoveAreaAsync(int areaId, int? newParentAreaId, string user)` adds area re-parenting (cycle prevention, same-site, name collision at new parent). Pairs with the existing `InstanceService.AssignToAreaAsync`.
|
||||
|
||||
**Instance rename:** Out of scope for this page. `InstanceService` does not currently support renaming an instance (`UniqueName` is also the site-side `InstanceActor` identity and appears in deployment records). A separate design pass is required if rename is wanted.
|
||||
|
||||
---
|
||||
|
||||
@@ -761,35 +779,7 @@ record TestNode(string Key, string Label, List<TestNode> Children);
|
||||
|
||||
---
|
||||
|
||||
### 3. Areas Page (`/admin/areas` — Areas.razor)
|
||||
|
||||
**Current state:** Two-panel layout. Left panel: site list (`list-group`). Right panel: manually indented flat tree of areas for the selected site, with `[+]`/`-` indicators, inline Edit/Delete buttons, and an add/edit form. Custom `BuildFlatTree()` / `AddChildren()` methods, `AreaTreeNode` record, manual `padding-left` indentation. ~293 lines.
|
||||
|
||||
**Change to:**
|
||||
- **Keep the two-panel layout** (site list on left, area tree on right).
|
||||
- Replace the custom flat-tree rendering in the right panel with a `<TreeView>` component.
|
||||
- **Site selection stays as-is** (left panel `list-group` click sets `_selectedSiteId`). This acts as the external filter — the TreeView receives only the selected site's areas as `Items`.
|
||||
- **Move Edit and Delete into the `ContextMenu` fragment** for area nodes:
|
||||
- Edit → loads area into the add/edit form (same as current behavior)
|
||||
- Delete → shows confirm dialog (with child check, same as current)
|
||||
- **Node content:** `<span>AreaName</span>` — optionally show instance count if available.
|
||||
- **Tree model:** For the selected site, load root areas (`ParentAreaId == null`), use `ChildrenSelector` to return child areas. The `Area` entity already has `Children` collection, so it can be used directly as `TItem` without a wrapper record — `ChildrenSelector = a => a.Children.ToList()`, `HasChildrenSelector = a => a.Children.Any()`, `KeySelector = a => a.Id`.
|
||||
- **Keep the add/edit form** at the top of the right panel (above the tree). The "Parent Area" dropdown stays.
|
||||
- **StorageKey:** `"areas-tree"`
|
||||
|
||||
**Files to modify:**
|
||||
- `src/ScadaLink.CentralUI/Components/Pages/Admin/Areas.razor` — replace custom flat-tree rendering with TreeView, remove `BuildFlatTree()`, `AddChildren()`, `AreaTreeNode` record, manual indentation CSS.
|
||||
|
||||
**Removed code:**
|
||||
- `BuildFlatTree()` method
|
||||
- `AddChildren()` recursive helper
|
||||
- `AreaTreeNode` record
|
||||
- Manual `padding-left` indentation
|
||||
- Custom `[+]`/`-` toggle rendering
|
||||
- Inline Edit/Delete buttons in the tree rows
|
||||
|
||||
## Interactions
|
||||
|
||||
- **DataTable**: The tree replaces flat tables on the three pages listed above. Other pages that don't need hierarchy continue using DataTable.
|
||||
- **InstanceConfigure.razor**: Right-click → Configure on an instance node navigates to `/deployment/instances/{Id}/configure`.
|
||||
- **Areas.razor**: The simplest integration — `Area` entity used directly as `TItem`, no wrapper needed.
|
||||
- **DataTable**: The tree replaces flat tables on the Topology and Data Connections pages. Other pages that don't need hierarchy continue using DataTable.
|
||||
- **InstanceConfigure.razor**: Right-click → Configure on an instance node navigates to `/deployment/instances/{Id}/configure`. Back-nav returns to `/deployment/topology`.
|
||||
|
||||
Reference in New Issue
Block a user