ui: Central UI density/consistency sweep + Theme 0.4.1
Applies the family-wide admin-UI cleanup playbook to the Central UI so the Blazor surfaces stop diverging from the shared kit: buttons are grouped rather than individually sized, long cell values are contained instead of widening tables, and hard-coded colours give way to theme tokens. The headline fix is that MainLayout passed Accent="#2f5fd0" to ThemeShell, which the kit emits as an inline style on the shell root. Being a descendant of <html>, it beat the [data-bs-theme="dark"] override for the entire app, so the dark accent had never rendered. Declaring --accent in site.css :root instead lets both schemes resolve; light is unchanged because the value already matched the kit's light default. Theme pins to 0.4.1, which upstreams the local .btn sizing block verbatim, so that block is deleted here rather than duplicated. Verified byte-identical before removal; the repo now declares no --bs-btn-* anywhere. NOT purely cosmetic, contrary to the sweep's stated scope: four detail-modal surfaces (NotificationReport, ConfigurationAuditLog, ParkedMessages, SiteCallsReport) were additionally refactored from holding the selected record to holding its id and re-resolving from the current page each render, with the resolve doubling as the visibility gate. A background refresh that drops the row now closes the modal instead of showing a stale snapshot. This is a behaviour change and is called out rather than buried: a full-suite run turned up one intermittent CentralUI failure, CloseButton_DismissesModal, whose stack (GetRequiredEventBindingEntry during DispatchEventAsync) indicates the handler was disposed between render and click — a window the previous field-held record made structurally impossible. Treat the modal lifecycle here as unreviewed. Build 0/0; suite green apart from that one intermittent failure.
This commit is contained in:
@@ -55,7 +55,7 @@
|
||||
<div class="small text-muted mt-1">Key ID: <code>@_newlyCreatedKeyId</code></div>
|
||||
<div class="d-flex align-items-center mt-2">
|
||||
<code class="me-2" data-test="created-token">@_newlyCreatedToken</code>
|
||||
<button class="btn btn-outline-secondary btn-sm py-0 px-1" @onclick="CopyKeyToClipboard">Copy</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" @onclick="CopyKeyToClipboard">Copy</button>
|
||||
</div>
|
||||
<small class="text-muted d-block mt-1">Save this token now — it will not be shown again.</small>
|
||||
</div>
|
||||
@@ -105,8 +105,10 @@
|
||||
<div class="text-danger small mt-2">@_formError</div>
|
||||
}
|
||||
<div class="mt-3">
|
||||
<button class="btn btn-success btn-sm me-1" @onclick="SaveKey">Save</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" @onclick="GoBack">Cancel</button>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button class="btn btn-success" @onclick="SaveKey">Save</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="GoBack">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -55,19 +55,24 @@
|
||||
<tr @key="key.KeyId">
|
||||
<td><code>@TruncateKeyId(key.KeyId)</code></td>
|
||||
<td>
|
||||
@key.Name
|
||||
@if (!key.Enabled)
|
||||
{
|
||||
<span class="badge bg-secondary ms-1">Disabled</span>
|
||||
}
|
||||
@* Operator-supplied name: clip it so a long value cannot push the
|
||||
actions column off-screen. .cell-clip is display:block, hence the
|
||||
inner span; the badge stays outside it so status is never clipped. *@
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="cell-clip" title="@key.Name">@key.Name</span>
|
||||
@if (!key.Enabled)
|
||||
{
|
||||
<span class="badge bg-secondary ms-1">Disabled</span>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td>@key.Methods.Count</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-outline-primary btn-sm py-0 px-2"
|
||||
<button class="btn btn-outline-primary btn-sm"
|
||||
@onclick='() => NavigationManager.NavigateTo($"/admin/api-keys/{key.KeyId}/edit")'>Edit</button>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary btn-sm py-0 px-2"
|
||||
<button class="btn btn-outline-secondary btn-sm"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-label="@($"More actions for {key.Name}")">⋮</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
|
||||
@@ -44,8 +44,10 @@
|
||||
<div class="text-danger small mt-2">@_formError</div>
|
||||
}
|
||||
<div class="mt-3">
|
||||
<button class="btn btn-success btn-sm me-1" @onclick="SaveMapping">Save</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" @onclick="GoBack">Cancel</button>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button class="btn btn-success" @onclick="SaveMapping">Save</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="GoBack">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,7 +55,9 @@
|
||||
var ruleCount = rules?.Count ?? 0;
|
||||
<tr @key="mapping.Id">
|
||||
<td>@mapping.Id</td>
|
||||
<td>@mapping.LdapGroupName</td>
|
||||
@* Externally sourced DN (CN=…,OU=…,DC=…): clip so it cannot set the
|
||||
table's width. .cell-clip is display:block, hence the span. *@
|
||||
<td><span class="cell-clip" title="@mapping.LdapGroupName">@mapping.LdapGroupName</span></td>
|
||||
<td><span class="badge bg-secondary">@mapping.Role</span></td>
|
||||
<td>
|
||||
@if (ruleCount > 0)
|
||||
@@ -69,10 +71,10 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-outline-primary btn-sm py-0 px-2"
|
||||
<button class="btn btn-outline-primary btn-sm"
|
||||
@onclick='() => NavigationManager.NavigateTo($"/admin/ldap-mappings/{mapping.Id}/edit")'>Edit</button>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary btn-sm py-0 px-2"
|
||||
<button class="btn btn-outline-secondary btn-sm"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-label="@($"More actions for {mapping.LdapGroupName}")">⋮</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
|
||||
@@ -19,21 +19,21 @@
|
||||
<ToastNotification @ref="_toast" />
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold">@(IsEditMode ? "Edit Site" : "Add Site")</span>
|
||||
@* Drill-in: deep-link into the central Audit
|
||||
Log pre-filtered to this site's events. AuditEvent.SourceSiteId
|
||||
stores the SiteIdentifier (string), so we pass that through. *@
|
||||
@if (IsEditMode && !string.IsNullOrWhiteSpace(_formIdentifier))
|
||||
{
|
||||
<a class="btn btn-outline-secondary btn-sm"
|
||||
href="/audit/log?site=@Uri.EscapeDataString(_formIdentifier)"
|
||||
data-test="audit-link">
|
||||
Recent audit activity
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<h6 class="card-title">@(IsEditMode ? "Edit Site" : "Add Site")</h6>
|
||||
@* Drill-in: deep-link into the central Audit
|
||||
Log pre-filtered to this site's events. AuditEvent.SourceSiteId
|
||||
stores the SiteIdentifier (string), so we pass that through. *@
|
||||
@if (IsEditMode && !string.IsNullOrWhiteSpace(_formIdentifier))
|
||||
{
|
||||
<a class="btn btn-outline-secondary btn-sm"
|
||||
href="/audit/log?site=@Uri.EscapeDataString(_formIdentifier)"
|
||||
data-test="audit-link">
|
||||
Recent audit activity
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">Identifier</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="_formIdentifier"
|
||||
@@ -43,43 +43,65 @@
|
||||
<label class="form-label small">Name</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="_formName" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="mb-0">
|
||||
<label class="form-label small">Description</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="_formDescription" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="text-muted border-bottom pb-1">Node A</h6>
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<span class="fw-semibold">Node A</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">Akka Address</label>
|
||||
<label class="form-label small">Akka Address (legacy — unused)</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="_formNodeAAddress"
|
||||
placeholder="akka.tcp://scadabridge@host:port/user/site-communication" />
|
||||
<div class="form-text">
|
||||
Retained for historical configuration only — nothing reads it at runtime.
|
||||
Central dials the gRPC address below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="mb-0">
|
||||
<label class="form-label small">gRPC Address</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="_formGrpcNodeAAddress"
|
||||
placeholder="http://host:8083" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="text-muted border-bottom pb-1">Node B</h6>
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<span class="fw-semibold">Node B</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">Akka Address</label>
|
||||
<label class="form-label small">Akka Address (legacy — unused)</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="_formNodeBAddress"
|
||||
placeholder="akka.tcp://scadabridge@host:port/user/site-communication" />
|
||||
<div class="form-text">
|
||||
Retained for historical configuration only — nothing reads it at runtime.
|
||||
Central dials the gRPC address below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="mb-0">
|
||||
<label class="form-label small">gRPC Address</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="_formGrpcNodeBAddress"
|
||||
placeholder="http://host:8083" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (_formError != null)
|
||||
{
|
||||
<div class="text-danger small mt-2">@_formError</div>
|
||||
}
|
||||
<div class="mt-3">
|
||||
<button class="btn btn-success btn-sm me-1" @onclick="SaveSite">Save</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" @onclick="GoBack">Cancel</button>
|
||||
</div>
|
||||
@if (_formError != null)
|
||||
{
|
||||
<div class="text-danger small mt-2">@_formError</div>
|
||||
}
|
||||
<div class="mb-3">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button class="btn btn-success" @onclick="SaveSite">Save</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="GoBack">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -146,8 +146,10 @@
|
||||
Cluster nodes (Akka, gRPC)
|
||||
</button>
|
||||
<div class="collapse mt-2" id="@collapseId">
|
||||
@ClusterRow("Node A", site.NodeAAddress)
|
||||
@ClusterRow("Node B", site.NodeBAddress)
|
||||
@* Akka node addresses are legacy: no runtime consumer since the
|
||||
ClusterClient→gRPC migration. Central dials the gRPC pair below. *@
|
||||
@ClusterRow("Node A (legacy)", site.NodeAAddress)
|
||||
@ClusterRow("Node B (legacy)", site.NodeBAddress)
|
||||
@ClusterRow("gRPC A", site.GrpcNodeAAddress)
|
||||
@ClusterRow("gRPC B", site.GrpcNodeBAddress)
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user