482d5f5637
Adopt the technical-light design system across the Admin web UI: - Vendor theme.css + IBM Plex woff2 fonts into wwwroot; include theme.css globally after Bootstrap. - Rebuild MainLayout: top app-bar (brand mark, breadcrumb, connection pill) + hairline-ruled side rail with accent-bordered active link. - Convert all 33 pages to the component catalog — tables to panel + data-table (num/mono columns), KPI cards to agg-grid, detail blocks to metric-card/kv rows, badges to chips, alerts to panel notice, headings to page-title/panel-head, .rise reveals. - Buttons/forms stay on Bootstrap; theme.css restyles them via --bs-* overrides. View-specific layout lives in app.css; all colour/type comes from theme.css tokens. Also fix a pre-existing /fleet 500: the node-state query ordered on a property of a constructed FleetNodeRow record, which EF Core cannot translate. Order the join's columns before projecting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
104 lines
3.6 KiB
Plaintext
104 lines
3.6 KiB
Plaintext
@page "/"
|
|
@using ZB.MOM.WW.OtOpcUa.Admin.Services
|
|
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
|
|
@inject ClusterService ClusterSvc
|
|
@inject GenerationService GenerationSvc
|
|
@inject NavigationManager Nav
|
|
|
|
<h1 class="page-title">Fleet overview</h1>
|
|
|
|
@if (_clusters is null)
|
|
{
|
|
<section class="panel rise" style="animation-delay:.02s">
|
|
<div class="panel-head">Loading</div>
|
|
<div style="padding:1.4rem;color:var(--ink-faint);font-style:italic">Loading fleet…</div>
|
|
</section>
|
|
}
|
|
else if (_clusters.Count == 0)
|
|
{
|
|
<section class="panel notice rise" style="animation-delay:.02s">
|
|
No clusters configured yet. <a href="/clusters/new">Create the first cluster</a>.
|
|
</section>
|
|
}
|
|
else
|
|
{
|
|
<section class="agg-grid rise" style="animation-delay:.02s">
|
|
<div class="agg-card">
|
|
<div class="agg-label">Clusters</div>
|
|
<div class="agg-value numeric">@_clusters.Count</div>
|
|
</div>
|
|
<div class="agg-card">
|
|
<div class="agg-label">Active drafts</div>
|
|
<div class="agg-value numeric">@_activeDraftCount</div>
|
|
</div>
|
|
<div class="agg-card">
|
|
<div class="agg-label">Published generations</div>
|
|
<div class="agg-value numeric">@_publishedCount</div>
|
|
</div>
|
|
<div class="agg-card @(_disabledCount > 0 ? "caution" : "")">
|
|
<div class="agg-label">Disabled clusters</div>
|
|
<div class="agg-value numeric">@_disabledCount</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="panel rise" style="animation-delay:.08s">
|
|
<div class="panel-head">Clusters</div>
|
|
<div class="table-wrap">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Cluster ID</th>
|
|
<th>Name</th>
|
|
<th>Enterprise / Site</th>
|
|
<th>Redundancy</th>
|
|
<th>State</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var c in _clusters)
|
|
{
|
|
<tr @onclick="@(() => Nav.NavigateTo($"/clusters/{c.ClusterId}"))">
|
|
<td class="mono">@c.ClusterId</td>
|
|
<td>@c.Name</td>
|
|
<td>@c.Enterprise / @c.Site</td>
|
|
<td class="mono">@c.RedundancyMode</td>
|
|
<td>
|
|
@if (c.Enabled)
|
|
{
|
|
<span class="chip chip-ok">enabled</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="chip chip-idle">disabled</span>
|
|
}
|
|
</td>
|
|
<td><a href="/clusters/@c.ClusterId">Open</a></td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
}
|
|
|
|
@code {
|
|
private List<ServerCluster>? _clusters;
|
|
private int _activeDraftCount;
|
|
private int _publishedCount;
|
|
private int _disabledCount;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
_clusters = await ClusterSvc.ListAsync(CancellationToken.None);
|
|
_disabledCount = _clusters.Count(c => !c.Enabled);
|
|
|
|
foreach (var c in _clusters)
|
|
{
|
|
var gens = await GenerationSvc.ListRecentAsync(c.ClusterId, 50, CancellationToken.None);
|
|
_activeDraftCount += gens.Count(g => g.Status.ToString() == "Draft");
|
|
_publishedCount += gens.Count(g => g.Status.ToString() == "Published");
|
|
}
|
|
}
|
|
}
|