(json, _jsonOpts); }
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Drivers/DriverTestConnectButton.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Drivers/DriverTestConnectButton.razor
new file mode 100644
index 00000000..1219b527
--- /dev/null
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Drivers/DriverTestConnectButton.razor
@@ -0,0 +1,82 @@
+@using ZB.MOM.WW.OtOpcUa.AdminUI.Clients
+@using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin
+@inject AdminProbeService Probe
+@implements IDisposable
+
+
+
+
+ @if (_result is not null)
+ {
+ @if (_result.Ok)
+ {
+
+ OK · @_result.LatencyMs?.ToString("F0") ms
+
+ }
+ else
+ {
+
+ Failed · @TruncatedMessage()
+
+ }
+ }
+
+
+@code {
+ /// Driver type key — must match an installed IDriverProbe.DriverType.
+ [Parameter, EditorRequired] public string DriverType { get; set; } = "";
+
+ /// Callback that returns the current form config as JSON. Called at click time.
+ [Parameter, EditorRequired] public Func GetConfigJson { get; set; } = () => "{}";
+
+ /// Per-probe timeout forwarded to the actor (actor clamps to [1, 60] s). Default 10 s.
+ [Parameter] public int TimeoutSeconds { get; set; } = 10;
+
+ private bool _busy;
+ private TestDriverConnectResult? _result;
+ private System.Timers.Timer? _clearTimer;
+
+ private async Task OnClickAsync()
+ {
+ _busy = true;
+ _result = null;
+ StateHasChanged();
+ try
+ {
+ var json = GetConfigJson() ?? "{}";
+ _result = await Probe.TestAsync(DriverType, json, TimeoutSeconds);
+ }
+ catch (Exception ex)
+ {
+ _result = new TestDriverConnectResult(false, ex.Message, null, Guid.Empty);
+ }
+ finally
+ {
+ _busy = false;
+ ScheduleClear();
+ StateHasChanged();
+ }
+ }
+
+ private void ScheduleClear()
+ {
+ _clearTimer?.Dispose();
+ _clearTimer = new System.Timers.Timer(30_000) { AutoReset = false };
+ _clearTimer.Elapsed += async (_, _) =>
+ {
+ _result = null;
+ await InvokeAsync(StateHasChanged);
+ };
+ _clearTimer.Start();
+ }
+
+ private string TruncatedMessage()
+ => _result?.Message is null ? "" :
+ (_result.Message.Length > 60 ? _result.Message[..60] + "…" : _result.Message);
+
+ public void Dispose() => _clearTimer?.Dispose();
+}