feat(adminui): F15.2 batch 2 — topology entity CRUD
v2-ci / build (push) Failing after 52s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (push) Has been skipped

Same single-page edit-or-create pattern as batch 1, applied to the
foundational topology entities. After this batch the whole hierarchy
(cluster → nodes → UNS areas → UNS lines → namespaces → drivers) is
fully editable through the UI.

- ClusterEdit.razor                  /clusters/{id}/edit
  Update + delete for an existing cluster. NodeCount stays coupled to
  RedundancyMode (None→1, Warm/Hot→2). ModifiedBy taken from
  AuthenticationStateProvider.
- NodeEdit.razor                     /clusters/{id}/nodes/{new|nodeId}
  Full ClusterNode CRUD. ApplicationUri uniqueness is enforced by EF
  index; ServiceLevelBase defaults to 200 (primary preference) on
  create; per-node DriverConfigOverridesJson validated as JSON.
- UnsAreaEdit.razor                  /clusters/{id}/uns/areas/{new|id}
- UnsLineEdit.razor                  /clusters/{id}/uns/lines/{new|id}
  UNS structure CRUD; Lines pick their parent Area from a select that
  loads the cluster's areas.

List pages updated:
- ClusterOverview now shows an "Edit cluster" button + a "New node"
  action on the nodes panel + per-row Edit buttons.
- ClusterUns gains New/Edit affordances for both Areas and Lines.

All 9 integration tests still green; no regressions.
This commit is contained in:
Joseph Doherty
2026-05-26 08:18:49 -04:00
parent 5ae67a48ba
commit 45740578c9
6 changed files with 842 additions and 6 deletions
@@ -26,7 +26,8 @@ else
<span class="mono text-muted">@_cluster.ClusterId</span>
@if (!_cluster.Enabled) { <span class="chip chip-idle ms-2">Disabled</span> }
</div>
<div>
<div class="d-flex gap-2">
<a href="/clusters/@ClusterId/edit" class="btn btn-outline-secondary btn-sm">Edit cluster</a>
<a href="/deployments" class="btn btn-outline-primary btn-sm">Deployments</a>
</div>
</div>
@@ -69,7 +70,10 @@ else
</section>
<section class="panel rise mt-3" style="animation-delay:.14s">
<div class="panel-head">Nodes</div>
<div class="panel-head d-flex align-items-center">
<span>Nodes</span>
<a href="/clusters/@ClusterId/nodes/new" class="btn btn-sm btn-outline-primary ms-auto">New node</a>
</div>
@if (_nodes is null || _nodes.Count == 0)
{
<div style="padding:1rem" class="text-muted">No nodes registered.</div>
@@ -85,6 +89,7 @@ else
<th>OPC UA port</th>
<th>ApplicationUri</th>
<th class="num">ServiceLevel base</th>
<th></th>
</tr>
</thead>
<tbody>
@@ -96,6 +101,7 @@ else
<td class="num">@n.OpcUaPort</td>
<td><span class="mono small">@n.ApplicationUri</span></td>
<td class="num">@n.ServiceLevelBase</td>
<td><a href="/clusters/@ClusterId/nodes/@n.NodeId" class="btn btn-sm btn-outline-primary">Edit</a></td>
</tr>
}
</tbody>