feat: restyle Admin UI with the technical-light design system
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>
This commit is contained in:
@@ -10,76 +10,80 @@
|
||||
|
||||
<PageTitle>Modbus diagnostics — @DriverInstanceId</PageTitle>
|
||||
|
||||
<div class="container py-4">
|
||||
<h1>Modbus auto-prohibitions</h1>
|
||||
<p class="text-muted">
|
||||
Driver instance <code>@DriverInstanceId</code>. Live snapshot of coalesced ranges
|
||||
the planner has learned to read individually (#148 / #150 / #151 / #152).
|
||||
</p>
|
||||
<h1 class="page-title">Modbus auto-prohibitions</h1>
|
||||
<p class="text-muted">
|
||||
Driver instance <span class="mono">@DriverInstanceId</span>. Live snapshot of coalesced ranges
|
||||
the planner has learned to read individually (#148 / #150 / #151 / #152).
|
||||
</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-sm btn-outline-primary" @onclick="LoadAsync" disabled="@_loading">
|
||||
@(_loading ? "Loading…" : "Refresh")
|
||||
</button>
|
||||
@if (_lastRefreshed is not null)
|
||||
{
|
||||
<span class="text-muted ms-3 small">Last refreshed @_lastRefreshed.Value.ToLocalTime().ToString("HH:mm:ss")</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (_error is not null)
|
||||
<div class="toolbar" style="margin-bottom:.75rem">
|
||||
<button class="btn btn-sm btn-outline-primary" @onclick="LoadAsync" disabled="@_loading">
|
||||
@(_loading ? "Loading…" : "Refresh")
|
||||
</button>
|
||||
@if (_lastRefreshed is not null)
|
||||
{
|
||||
<div class="alert alert-danger">@_error</div>
|
||||
}
|
||||
else if (_response is null)
|
||||
{
|
||||
<p class="text-muted">Click <strong>Refresh</strong> to load.</p>
|
||||
}
|
||||
else if (_response.Count == 0)
|
||||
{
|
||||
<div class="alert alert-success">No auto-prohibitions. The planner is coalescing freely.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Unit</th>
|
||||
<th>Region</th>
|
||||
<th>Start</th>
|
||||
<th>End</th>
|
||||
<th>Span</th>
|
||||
<th>Status</th>
|
||||
<th>Last probed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var r in _response.Ranges.OrderBy(r => r.UnitId).ThenBy(r => r.Region).ThenBy(r => r.StartAddress))
|
||||
{
|
||||
<tr>
|
||||
<td><code>@r.UnitId</code></td>
|
||||
<td><code>@r.Region</code></td>
|
||||
<td><code>@r.StartAddress</code></td>
|
||||
<td><code>@r.EndAddress</code></td>
|
||||
<td>@(r.EndAddress - r.StartAddress + 1)</td>
|
||||
<td>
|
||||
@if (r.BisectionPending)
|
||||
{
|
||||
<span class="badge bg-warning text-dark">BISECTING</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-danger">ISOLATED</span>
|
||||
}
|
||||
</td>
|
||||
<td class="small text-muted">@FormatTimeSince(r.LastProbedUtc)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<span class="text-muted ms-3 small">Last refreshed @_lastRefreshed.Value.ToLocalTime().ToString("HH:mm:ss")</span>
|
||||
}
|
||||
<span class="spacer"></span>
|
||||
</div>
|
||||
|
||||
@if (_error is not null)
|
||||
{
|
||||
<section class="panel notice rise" style="animation-delay:.02s">@_error</section>
|
||||
}
|
||||
else if (_response is null)
|
||||
{
|
||||
<p class="text-muted">Click <strong>Refresh</strong> to load.</p>
|
||||
}
|
||||
else if (_response.Count == 0)
|
||||
{
|
||||
<section class="panel notice rise" style="animation-delay:.02s">No auto-prohibitions. The planner is coalescing freely.</section>
|
||||
}
|
||||
else
|
||||
{
|
||||
<section class="panel rise" style="animation-delay:.02s">
|
||||
<div class="panel-head">Prohibited ranges</div>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Unit</th>
|
||||
<th>Region</th>
|
||||
<th class="num">Start</th>
|
||||
<th class="num">End</th>
|
||||
<th class="num">Span</th>
|
||||
<th>Status</th>
|
||||
<th>Last probed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var r in _response.Ranges.OrderBy(r => r.UnitId).ThenBy(r => r.Region).ThenBy(r => r.StartAddress))
|
||||
{
|
||||
<tr>
|
||||
<td class="mono">@r.UnitId</td>
|
||||
<td class="mono">@r.Region</td>
|
||||
<td class="num mono">@r.StartAddress</td>
|
||||
<td class="num mono">@r.EndAddress</td>
|
||||
<td class="num">@(r.EndAddress - r.StartAddress + 1)</td>
|
||||
<td>
|
||||
@if (r.BisectionPending)
|
||||
{
|
||||
<span class="chip chip-warn">BISECTING</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="chip chip-bad">ISOLATED</span>
|
||||
}
|
||||
</td>
|
||||
<td class="small text-muted">@FormatTimeSince(r.LastProbedUtc)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public string DriverInstanceId { get; set; } = string.Empty;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user