ModbusAddressPreview (@bind on dropdowns + child ModbusAddressEditor with @oninput), ModbusDiagnostics (@onclick Refresh), and NewCluster (EditForm with Nav.NavigateTo on submit) were missed in the first pass — all three require interactivity but had no @rendermode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
127 lines
4.5 KiB
Plaintext
127 lines
4.5 KiB
Plaintext
@page "/modbus/diagnostics/{DriverInstanceId}"
|
|
@using Microsoft.AspNetCore.Components.Web
|
|
@using ZB.MOM.WW.OtOpcUa.Admin.Services
|
|
@rendermode RenderMode.InteractiveServer
|
|
@inject DriverDiagnosticsClient Diagnostics
|
|
|
|
@*
|
|
#154 — operator-facing view of the Server's auto-prohibition state for a Modbus driver.
|
|
Fetches via DriverDiagnosticsClient (HttpClient against the Server's HealthEndpointsHost).
|
|
Refreshes on demand; auto-refresh is a future task once a SignalR diag channel exists.
|
|
*@
|
|
|
|
<PageTitle>Modbus diagnostics — @DriverInstanceId</PageTitle>
|
|
|
|
<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="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)
|
|
{
|
|
<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;
|
|
|
|
private ModbusAutoProhibitionsResponse? _response;
|
|
private string? _error;
|
|
private bool _loading;
|
|
private DateTime? _lastRefreshed;
|
|
|
|
private async Task LoadAsync()
|
|
{
|
|
_loading = true;
|
|
_error = null;
|
|
try
|
|
{
|
|
_response = await Diagnostics.GetModbusAutoProhibitedRangesAsync(DriverInstanceId);
|
|
_lastRefreshed = DateTime.UtcNow;
|
|
if (_response is null)
|
|
_error = $"Server reports driver '{DriverInstanceId}' is not present or is not a Modbus driver.";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_error = $"Fetch failed: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
_loading = false;
|
|
}
|
|
}
|
|
|
|
private static string FormatTimeSince(DateTime utc)
|
|
{
|
|
var span = DateTime.UtcNow - utc;
|
|
if (span.TotalSeconds < 60) return $"{(int)span.TotalSeconds}s ago";
|
|
if (span.TotalMinutes < 60) return $"{(int)span.TotalMinutes}m ago";
|
|
if (span.TotalHours < 24) return $"{(int)span.TotalHours}h ago";
|
|
return $"{(int)span.TotalDays}d ago";
|
|
}
|
|
}
|