8a6f0e3c84
Live gate against the cppagent 2.7 fixture: count=16384 with bufferSize
16384 streams; 16385 is OUT_OF_RANGE ('count' must be less than 16385).
Warn only when SampleCount > bufferSize, pin both sides of the edge
against the live Agent, and correct the #804 wording that said 'at or
above'.
132 lines
5.1 KiB
Plaintext
132 lines
5.1 KiB
Plaintext
@using ZB.MOM.WW.OtOpcUa.AdminUI.Clients
|
|
@using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin
|
|
@inject AdminProbeService Probe
|
|
@implements IAsyncDisposable
|
|
|
|
<div class="d-inline-flex align-items-center gap-2">
|
|
<button type="button" class="btn btn-outline-primary btn-sm" disabled="@_busy" @onclick="OnClickAsync"
|
|
title="@(_lastBudgetSeconds is { } b ? $"Probe budget: {b} s" : "Probe budget: the driver's configured probe timeout")">
|
|
@if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
|
|
Test Connect
|
|
</button>
|
|
|
|
@if (_result is not null)
|
|
{
|
|
@if (_result.Ok && WarningOf(_result.Message) is { } warning)
|
|
{
|
|
@* Reachable but misconfigured (e.g. MTConnect SampleCount > the Agent's bufferSize, #825): a probe
|
|
that answers Ok with a "WARNING:" message is shown amber with the warning, never a plain green OK. *@
|
|
<span class="chip chip-warn" title="@warning">
|
|
OK with warning · @_result.LatencyMs?.ToString("F0") ms
|
|
</span>
|
|
<div class="small text-warning mt-1">@warning</div>
|
|
}
|
|
else if (_result.Ok)
|
|
{
|
|
<span class="chip chip-ok" title="@($"Probe succeeded in {_result.LatencyMs:F0} ms")">
|
|
OK · @_result.LatencyMs?.ToString("F0") ms
|
|
</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="chip chip-bad" title="@_result.Message">
|
|
Failed · @DisplayText.Abbreviate(_result.Message, 60)
|
|
</span>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
/// <summary>The marker a probe puts in an <c>Ok</c> message to flag a warning (see <c>MTConnectDriverProbe</c>).</summary>
|
|
internal const string WarningMarker = "WARNING:";
|
|
|
|
/// <summary>The warning text after <see cref="WarningMarker"/>, or null when the message carries none.</summary>
|
|
internal static string? WarningOf(string? message)
|
|
{
|
|
var at = message?.IndexOf(WarningMarker, StringComparison.Ordinal) ?? -1;
|
|
return at < 0 ? null : message![(at + WarningMarker.Length)..].Trim();
|
|
}
|
|
|
|
/// <summary>Driver type key — must match an installed <c>IDriverProbe.DriverType</c>.</summary>
|
|
[Parameter, EditorRequired] public string DriverType { get; set; } = "";
|
|
|
|
/// <summary>Callback that returns the current form config as JSON. Called at click time.</summary>
|
|
[Parameter, EditorRequired] public Func<string> GetConfigJson { get; set; } = () => "{}";
|
|
|
|
/// <summary>
|
|
/// Fallback probe budget, used only when the config JSON carries no positive
|
|
/// <c>ProbeTimeoutSeconds</c> and the driver has no documented default (<c>TestConnectBudget.DriverDefaults</c>). The configured value wins (it is every driver's documented "Test Connect
|
|
/// probe timeout"), so it can lengthen the budget as well as shorten it. Clamped to [1, 60] s.
|
|
/// </summary>
|
|
[Parameter] public int TimeoutSeconds { get; set; } = TestConnectBudget.DefaultSeconds;
|
|
|
|
private const int ClearAfterMs = 30_000;
|
|
|
|
private bool _busy;
|
|
private int? _lastBudgetSeconds;
|
|
private TestDriverConnectResult? _result;
|
|
|
|
// System.Threading.Timer (it replaced an Elapsed-event timer with an async void lambda): DisposeAsync awaits an
|
|
// in-flight callback, and the callback body never throws onto the thread pool (R04 L2). The generation
|
|
// makes a superseded timer's late fire a no-op, so it cannot clear a newer result.
|
|
private System.Threading.Timer? _clearTimer;
|
|
private int _resultGeneration;
|
|
private volatile bool _disposed;
|
|
|
|
private async Task OnClickAsync()
|
|
{
|
|
_busy = true;
|
|
_result = null;
|
|
StateHasChanged();
|
|
try
|
|
{
|
|
var json = GetConfigJson() ?? "{}";
|
|
var budget = TestConnectBudget.Resolve(json, TimeoutSeconds, DriverType);
|
|
_lastBudgetSeconds = budget;
|
|
_result = await Probe.TestAsync(DriverType, json, budget);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_result = new TestDriverConnectResult(false, ex.Message, null, Guid.Empty);
|
|
}
|
|
finally
|
|
{
|
|
_busy = false;
|
|
ScheduleClear();
|
|
if (!_disposed) StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private void ScheduleClear()
|
|
{
|
|
if (_disposed) return;
|
|
var generation = ++_resultGeneration;
|
|
_clearTimer?.Dispose();
|
|
_clearTimer = new System.Threading.Timer(_ => _ = ClearAsync(generation), null, ClearAfterMs, Timeout.Infinite);
|
|
}
|
|
|
|
private async Task ClearAsync(int generation)
|
|
{
|
|
try
|
|
{
|
|
await InvokeAsync(() =>
|
|
{
|
|
if (_disposed || generation != _resultGeneration) return;
|
|
_result = null;
|
|
StateHasChanged();
|
|
});
|
|
}
|
|
catch (Exception ex) when (ex is ObjectDisposedException or InvalidOperationException)
|
|
{
|
|
// The circuit went away between the fire and the render — nothing to clear.
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
_disposed = true;
|
|
if (_clearTimer is not null) await _clearTimer.DisposeAsync();
|
|
}
|
|
}
|