merge: AdminUI sweep — Clusters pages (btn-groups, notice-alert, stale-prose deletions)
This commit is contained in:
@@ -31,32 +31,6 @@ else if (!IsNew && _existing is null)
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info" role="alert">
|
||||
<strong>What this grant does depends on <span class="mono">Security:Acl:Enabled</span> on the
|
||||
node serving the data.</strong> That is per-node configuration, so this page cannot tell you
|
||||
which mode a given node is in — check the node's own settings.
|
||||
<br />
|
||||
<strong>With it on:</strong> the scope is honoured, and the scoped decision <em>replaces</em> the
|
||||
server-wide role rather than adding to it. <strong>Every data-plane operation is gated</strong> —
|
||||
<span class="mono">Read</span>, <span class="mono">Browse</span>,
|
||||
<span class="mono">Subscribe</span>, <span class="mono">WriteOperate</span>,
|
||||
<span class="mono">HistoryRead</span> and the alarm methods, which separate into
|
||||
<span class="mono">AlarmAcknowledge</span> / <span class="mono">AlarmConfirm</span> /
|
||||
<span class="mono">AlarmShelve</span>. So a grant that omits <span class="mono">Read</span>
|
||||
denies reads, and one that omits <span class="mono">Browse</span> hides the node from navigation.
|
||||
Granting <span class="mono">Browse</span> on a node also makes the folders <em>above</em> it
|
||||
browsable — otherwise the grant could never be navigated to — so it reveals their names.
|
||||
Revoking this grant stops any live subscription it was authorizing, on the next deployment.
|
||||
<br />
|
||||
<strong>With it off (the default):</strong> only a <span class="mono">Cluster</span>-scoped grant
|
||||
does anything, and it confers a <em>server-wide</em> role on its principal —
|
||||
<span class="mono">WriteOperate</span> → writing any tag value;
|
||||
<span class="mono">AlarmAcknowledge</span> → the Part 9 alarm methods. Any narrower scope is
|
||||
stored and shipped but <strong>confers nothing</strong>. That under-granting is deliberate:
|
||||
honouring a grant's flags while its <em>scope</em> cannot be honoured would turn "WriteOperate on
|
||||
one equipment" into server-wide write authority.
|
||||
</div>
|
||||
|
||||
<EditForm Model="_form" OnValidSubmit="SubmitAsync" FormName="aclEdit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary class="alert alert-danger" />
|
||||
@@ -151,13 +125,12 @@ else
|
||||
</div>
|
||||
<div class="form-text mt-2">
|
||||
Bundles:
|
||||
<button type="button" class="btn btn-sm btn-link p-0 ms-1" @onclick="() => _form.PermissionFlags = NodePermissions.ReadOnly">ReadOnly</button>
|
||||
·
|
||||
<button type="button" class="btn btn-sm btn-link p-0" @onclick="() => _form.PermissionFlags = NodePermissions.Operator">Operator</button>
|
||||
·
|
||||
<button type="button" class="btn btn-sm btn-link p-0" @onclick="() => _form.PermissionFlags = NodePermissions.Engineer">Engineer</button>
|
||||
·
|
||||
<button type="button" class="btn btn-sm btn-link p-0" @onclick="() => _form.PermissionFlags = NodePermissions.Admin">Admin</button>
|
||||
<div class="btn-group btn-group-sm ms-1" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="() => _form.PermissionFlags = NodePermissions.ReadOnly">ReadOnly</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="() => _form.PermissionFlags = NodePermissions.Operator">Operator</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="() => _form.PermissionFlags = NodePermissions.Engineer">Engineer</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="() => _form.PermissionFlags = NodePermissions.Admin">Admin</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@@ -167,11 +140,13 @@ else
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_error)) { <div class="panel notice mt-3" style="border-color:var(--alert)">@_error</div> }
|
||||
@if (!string.IsNullOrWhiteSpace(_error)) { <div class="panel notice notice-alert mt-3">@_error</div> }
|
||||
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary" disabled="@_busy">@(IsNew ? "Create" : "Save changes")</button>
|
||||
<a href="/clusters/@ClusterId/acls" class="btn btn-outline-secondary">Cancel</a>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="submit" class="btn btn-primary" disabled="@_busy">@(IsNew ? "Create" : "Save changes")</button>
|
||||
<a href="/clusters/@ClusterId/acls" class="btn btn-outline-secondary">Cancel</a>
|
||||
</div>
|
||||
@if (!IsNew) { <button type="button" class="btn btn-outline-danger ms-auto" @onclick="DeleteAsync" disabled="@_busy">Delete</button> }
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@@ -21,49 +21,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info" role="alert">
|
||||
<strong>What these grants do depends on
|
||||
<span class="mono">Security:Acl:Enabled</span> on the node serving the data.</strong>
|
||||
That is per-node configuration, so this page cannot tell you which mode a given node is in —
|
||||
check the node's own settings.
|
||||
<br />
|
||||
<strong>With it on:</strong> every scope kind is honoured, and the scoped decision
|
||||
<em>replaces</em> the server-wide role rather than adding to it — so a narrow grant works, and a
|
||||
Cluster-scoped one no longer confers authority beyond that cluster.
|
||||
<strong>Every data-plane operation is gated</strong> — <span class="mono">Read</span>,
|
||||
<span class="mono">Browse</span>, <span class="mono">Subscribe</span>,
|
||||
<span class="mono">WriteOperate</span>, <span class="mono">HistoryRead</span> and the alarm
|
||||
methods, which separate into <span class="mono">AlarmAcknowledge</span> /
|
||||
<span class="mono">AlarmConfirm</span> / <span class="mono">AlarmShelve</span>. A principal with
|
||||
no grant at all can connect and see nothing.
|
||||
<br />
|
||||
<strong>With it off (the default):</strong> only <span class="mono">Cluster</span>-scoped grants
|
||||
do anything, and they confer a <em>server-wide</em> role on their principal —
|
||||
<span class="mono">WriteOperate</span> → writing any tag value;
|
||||
<span class="mono">AlarmAcknowledge</span> → the Part 9 alarm methods
|
||||
(acknowledge / confirm / shelve). Narrower grants are saved and shipped in every deployment
|
||||
artifact but confer <strong>nothing</strong>. That under-granting is deliberate: honouring such
|
||||
a grant's flags while its <em>scope</em> cannot be honoured would turn "WriteOperate on one
|
||||
equipment" into server-wide write authority.
|
||||
<br />
|
||||
A node that merely <em>leads to</em> something you may browse stays browsable, or a narrow grant
|
||||
could never be navigated to — so granting <span class="mono">Browse</span> deep in the tree also
|
||||
reveals the names of the folders above it. Revoking a grant stops live subscriptions on the next
|
||||
deployment, not just future requests.
|
||||
<br />
|
||||
Identity is an <strong>API key</strong>, not LDAP: a client presents an OPC UA UserName token
|
||||
with username <span class="mono">apikey</span> and password
|
||||
<span class="mono">otua_<keyId>_<secret></span>, and the key resolves to the
|
||||
principal named on these rows. Mint keys on the <span class="mono">/apikeys</span> page; LDAP
|
||||
now covers AdminUI login only. See <span class="mono">docs/security.md</span>.
|
||||
</div>
|
||||
|
||||
<section class="panel notice rise" style="animation-delay:.02s">
|
||||
ACL rows grant principals specific <span class="mono">NodePermissions</span> on a scope
|
||||
(the cluster, a namespace, an area, an equipment, a raw device, a tag). A principal is an API
|
||||
key's principal, or the reserved <span class="mono">anonymous</span> principal that an
|
||||
anonymous session authenticates as.
|
||||
<br />
|
||||
The <strong>Scope target</strong> column resolves each row against the current configuration. A
|
||||
row marked <span class="chip chip-alert">unresolved</span> points at nothing — it is shipped in
|
||||
every deployment artifact but authorizes nobody, and the deploy gate rejects it. Open it and
|
||||
@@ -75,10 +33,7 @@ else
|
||||
@if (_rows.Count == 0)
|
||||
{
|
||||
<div style="padding:1rem" class="text-muted">
|
||||
No ACL rows for this cluster. With <span class="mono">Security:Acl:Enabled</span> off, that
|
||||
means writes and alarm acknowledges are refused while reads, browses, subscriptions and
|
||||
history reads stay open to any admitted session. With it <strong>on</strong>, it means
|
||||
every data-plane operation is refused here — a client can connect and see nothing.
|
||||
No ACL rows for this cluster.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
else
|
||||
{
|
||||
<section class="panel notice rise" style="animation-delay:.02s">
|
||||
Latest @PageSize audit rows scoped to this cluster, newest first. EventId/CorrelationId
|
||||
columns (F3) make cross-restart deduplication possible — Akka actors that retry an apply
|
||||
won't insert duplicate rows. Details JSON is shown verbatim.
|
||||
Latest @PageSize audit rows scoped to this cluster, newest first. Details JSON is shown verbatim.
|
||||
</section>
|
||||
|
||||
<section class="panel rise mt-3" style="animation-delay:.08s">
|
||||
|
||||
@@ -86,15 +86,17 @@ else
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_error))
|
||||
{
|
||||
<div class="panel notice mt-3" style="border-color:var(--alert)">@_error</div>
|
||||
<div class="panel notice notice-alert mt-3">@_error</div>
|
||||
}
|
||||
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary" disabled="@_busy">
|
||||
@if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
|
||||
Save changes
|
||||
</button>
|
||||
<a href="/clusters/@ClusterId" class="btn btn-outline-secondary">Cancel</a>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="submit" class="btn btn-primary" disabled="@_busy">
|
||||
@if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
|
||||
Save changes
|
||||
</button>
|
||||
<a href="/clusters/@ClusterId" class="btn btn-outline-secondary">Cancel</a>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-danger ms-auto" @onclick="DeleteAsync" disabled="@_busy">
|
||||
Delete cluster
|
||||
</button>
|
||||
|
||||
+1
-2
@@ -13,8 +13,7 @@
|
||||
<div style="padding:1rem">
|
||||
The <code>Namespace</code> entity has been removed. The v3 Raw/UNS split replaces it —
|
||||
raw driver folders and the unified namespace are managed on the <strong>/raw</strong> and
|
||||
<strong>/uns</strong> pages, arriving in v3 Batch 2/3. This page is a placeholder so existing
|
||||
links do not 404.
|
||||
<strong>/uns</strong> pages. This page is a placeholder so existing links do not 404.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ else
|
||||
<span class="mono text-muted">@_cluster.ClusterId</span>
|
||||
@if (!_cluster.Enabled) { <span class="chip chip-idle ms-2">Disabled</span> }
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<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>
|
||||
|
||||
+5
-15
@@ -34,15 +34,6 @@ else
|
||||
|
||||
<ClusterNav ClusterId="@ClusterId" ActiveTab="redundancy" />
|
||||
|
||||
<section class="panel notice rise" style="animation-delay:.02s">
|
||||
v2 redundancy is computed at runtime by <span class="mono">RedundancyStateActor</span>
|
||||
on each admin node. The values below are the static configuration; the resolved live
|
||||
<span class="mono">ServiceLevel</span> for each peer is broadcast on the
|
||||
<span class="mono">redundancy-state</span> DPS topic and consumed by the OPC UA host's
|
||||
<span class="mono">ServerStatus</span> publisher. See
|
||||
<a href="/docs/v2/Architecture-v2.md">docs/v2/Architecture-v2.md</a>.
|
||||
</section>
|
||||
|
||||
<section class="card-grid rise mt-3" style="animation-delay:.08s">
|
||||
<div class="metric-card">
|
||||
<div class="panel-head">Cluster redundancy</div>
|
||||
@@ -68,10 +59,7 @@ else
|
||||
</div>
|
||||
|
||||
<p class="text-muted small mt-2 mb-0">
|
||||
Read live from cluster state on this node. The Primary shown is
|
||||
<strong>this application <span class="mono">Cluster</span>'s own Primary</strong> — since
|
||||
the per-cluster mesh split, each cluster's redundant pair runs its own independent 2-node
|
||||
Akka mesh and elects its own Primary. See <span class="mono">docs/Redundancy.md</span>.
|
||||
Read live from cluster state on this node.
|
||||
</p>
|
||||
|
||||
<AuthorizeView Policy="@ManualFailoverPageModel.RequiredPolicy">
|
||||
@@ -98,8 +86,10 @@ else
|
||||
<li>Its peer becomes Primary and advertises <span class="mono">ServiceLevel</span> 250.</li>
|
||||
<li>Connected OPC UA clients re-select the new Primary.</li>
|
||||
</ul>
|
||||
<button class="btn btn-sm btn-danger" @onclick="ConfirmFailoverAsync">Confirm failover</button>
|
||||
<button class="btn btn-sm btn-outline-secondary ms-2" @onclick="() => _failover.CancelFailover()">Cancel</button>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button class="btn btn-sm btn-danger" @onclick="ConfirmFailoverAsync">Confirm failover</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" @onclick="() => _failover.CancelFailover()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Authorized>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<div style="padding:1rem">
|
||||
The <code>Namespace</code> entity has been removed. The v3 Raw/UNS split replaces it —
|
||||
raw driver folders and the unified namespace are managed on the <strong>/raw</strong> and
|
||||
<strong>/uns</strong> pages, arriving in v3 Batch 2/3. This editor no longer applies.
|
||||
<strong>/uns</strong> pages. This editor no longer applies.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -69,11 +69,13 @@
|
||||
}
|
||||
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary" disabled="@_busy">
|
||||
@if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
|
||||
Create cluster
|
||||
</button>
|
||||
<a href="/clusters" class="btn btn-outline-secondary">Cancel</a>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="submit" class="btn btn-primary" disabled="@_busy">
|
||||
@if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
|
||||
Create cluster
|
||||
</button>
|
||||
<a href="/clusters" class="btn btn-outline-secondary">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
|
||||
@@ -102,15 +102,17 @@ else
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_error))
|
||||
{
|
||||
<div class="panel notice mt-3" style="border-color:var(--alert)">@_error</div>
|
||||
<div class="panel notice notice-alert mt-3">@_error</div>
|
||||
}
|
||||
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary" disabled="@_busy">
|
||||
@if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
|
||||
@(IsNew ? "Create" : "Save changes")
|
||||
</button>
|
||||
<a href="/clusters/@ClusterId" class="btn btn-outline-secondary">Cancel</a>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="submit" class="btn btn-primary" disabled="@_busy">
|
||||
@if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
|
||||
@(IsNew ? "Create" : "Save changes")
|
||||
</button>
|
||||
<a href="/clusters/@ClusterId" class="btn btn-outline-secondary">Cancel</a>
|
||||
</div>
|
||||
@if (!IsNew)
|
||||
{
|
||||
<button type="button" class="btn btn-outline-danger ms-auto" @onclick="DeleteAsync" disabled="@_busy">
|
||||
|
||||
Reference in New Issue
Block a user