This connection was migrated from a legacy format.
Review the settings and Save to update.
}
@* Each settings group gets a real boundary (fieldset + legend) rather than an
unstyled muted caption. Fieldsets — not nested cards — because this editor is
embedded inside DataConnectionForm's card; same idiom as TransportExport. *@
@code {
[Parameter, EditorRequired] public OpcUaEndpointConfig Config { get; set; } = default!;
[Parameter] public string Title { get; set; } = "Endpoint";
[Parameter] public string IdPrefix { get; set; } = "endpoint";
[Parameter] public bool IsLegacy { get; set; }
[Parameter] public ValidationResult? Errors { get; set; }
// Verify-endpoint context (M7): the site + connection identity the verify
// probe targets. Supplied by DataConnectionForm (_formSiteId → SiteIdentifier,
// _formName, _protocol). When SiteIdentifier is blank the connection has not been
// assigned a site yet, so verification is unavailable.
[Parameter] public string SiteIdentifier { get; set; } = string.Empty;
[Parameter] public string ConnectionName { get; set; } = string.Empty;
[Parameter] public string Protocol { get; set; } = "OpcUa";
private bool _verifying;
private VerifyEndpointResult? _verifyResult;
private bool _trusting;
private bool _trustSucceeded;
private string? _trustError;
private Task VerifyEndpoint() => VerifyEndpoint(clearTrustNotes: true);
private async Task VerifyEndpoint(bool clearTrustNotes)
{
_verifying = true;
_verifyResult = null;
// A fresh (user-initiated) verify supersedes any prior trust outcome — clear
// the inline notes so a stale "trusted" / error banner doesn't linger across
// probes. The trust-triggered re-verify keeps the success note so the operator
// still sees confirmation even if the re-probe also surfaces a (different) cert.
if (clearTrustNotes)
{
_trustSucceeded = false;
_trustError = null;
}
try
{
_verifyResult = await VerificationService.VerifyAsync(
SiteIdentifier, ConnectionName, Protocol, Config);
}
catch (Exception ex)
{
_verifyResult = new VerifyEndpointResult(
false, VerifyFailureKind.ServerError, ex.Message, null);
}
finally
{
_verifying = false;
}
}
// M7: trust the captured untrusted server certificate at every node of the
// owning site, then re-run Verify (which should now succeed and clear the cert
// panel). Administrator-gated by the AuthorizeView wrapping the button; the
// CertManagementService enforces the same role server-side-of-the-trust-boundary.
private async Task TrustCert(ServerCertInfo cert)
{
_trusting = true;
_trustError = null;
_trustSucceeded = false;
try
{
var result = await CertManagementService.TrustAsync(
SiteIdentifier, ConnectionName, cert.DerBase64, cert.Thumbprint);
if (result.Success)
{
_trustSucceeded = true;
// Re-run Verify so the editor reflects the now-trusted state (and
// clears the cert panel on the expected success). Preserve the
// success note (clearTrustNotes:false) so confirmation is shown.
await VerifyEndpoint(clearTrustNotes: false);
}
else
{
_trustError = result.Error ?? "Failed to trust certificate.";
}
}
catch (Exception ex)
{
_trustError = ex.Message;
}
finally
{
_trusting = false;
}
}
private void EnableHeartbeat() =>
Config.Heartbeat = new OpcUaHeartbeatConfig();
private void EnableAuthentication() =>
Config.UserIdentity = new OpcUaUserIdentityConfig();
private void EnableDeadband() =>
Config.Deadband = new OpcUaDeadbandConfig();
private RenderFragment? RenderFieldError(string field)
{
var match = Errors?.Errors.FirstOrDefault(e =>
e.EntityName != null
&& (e.EntityName == field || e.EntityName.EndsWith("." + field)));
return match is null
? null
: @