cb0d17dabd
IOpcUaBrowseService/OpcUaBrowseService -> IBrowseService/BrowseService, OpcUaBrowserDialog -> NodeBrowserDialog, and neutralize 'Browse OPC UA' UI strings to 'Browse'. Updates DI, InstanceConfigure, TestBindingsDialog, TreeRow, BindingTester, and tests. 574 CentralUI tests green.
82 lines
3.4 KiB
C#
82 lines
3.4 KiB
C#
using Microsoft.AspNetCore.Components.Authorization;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
|
|
using ZB.MOM.WW.ScadaBridge.Communication;
|
|
using ZB.MOM.WW.ScadaBridge.Security;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
|
|
|
/// <summary>
|
|
/// Default <see cref="IBindingTester"/> implementation — a thin facade over
|
|
/// <see cref="CommunicationService.ReadTagValuesAsync"/> that enforces the
|
|
/// CentralUI-side <c>Design</c>-role trust boundary and translates transport
|
|
/// exceptions into a typed <see cref="ReadTagValuesFailure"/> result. Mirrors
|
|
/// <see cref="BrowseService"/>.
|
|
/// </summary>
|
|
public sealed class BindingTester : IBindingTester
|
|
{
|
|
private readonly CommunicationService _communication;
|
|
private readonly AuthenticationStateProvider _auth;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="BindingTester"/>.
|
|
/// </summary>
|
|
/// <param name="communication">Central-side cluster communication service.</param>
|
|
/// <param name="auth">Authentication state provider used for the Design-role guard.</param>
|
|
public BindingTester(CommunicationService communication, AuthenticationStateProvider auth)
|
|
{
|
|
_communication = communication ?? throw new ArgumentNullException(nameof(communication));
|
|
_auth = auth ?? throw new ArgumentNullException(nameof(auth));
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<ReadTagValuesResult> ReadAsync(
|
|
string siteId,
|
|
string connectionName,
|
|
IReadOnlyList<string> tagPaths,
|
|
CancellationToken ct = default)
|
|
{
|
|
// CentralUI-side role guard — sites don't enforce envelope-level
|
|
// roles, so the Design check must happen here before any cross-cluster
|
|
// traffic. Use HasClaim against JwtTokenService.RoleClaimType (not
|
|
// IsInRole, per c1e16cf).
|
|
var state = await _auth.GetAuthenticationStateAsync();
|
|
if (!state.User.HasClaim(JwtTokenService.RoleClaimType, "Design"))
|
|
{
|
|
return new ReadTagValuesResult(
|
|
Array.Empty<TagReadOutcome>(),
|
|
new ReadTagValuesFailure(ReadTagValuesFailureKind.ServerError, "Not authorized."));
|
|
}
|
|
|
|
try
|
|
{
|
|
return await _communication.ReadTagValuesAsync(
|
|
siteId,
|
|
new ReadTagValuesCommand(connectionName, tagPaths),
|
|
ct);
|
|
}
|
|
catch (TimeoutException ex)
|
|
{
|
|
// Akka Ask timed out — the site (or its OPC UA session) didn't
|
|
// answer within CommunicationOptions.QueryTimeout. Surface as a
|
|
// typed Timeout failure so the dialog can render an inline banner.
|
|
return new ReadTagValuesResult(
|
|
Array.Empty<TagReadOutcome>(),
|
|
new ReadTagValuesFailure(ReadTagValuesFailureKind.Timeout, ex.Message));
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// Caller-initiated cancel — propagate so Blazor can drop the
|
|
// response cleanly. Distinct from Timeout.
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Any other transport / serialization failure: keep the dialog
|
|
// alive with a typed banner.
|
|
return new ReadTagValuesResult(
|
|
Array.Empty<TagReadOutcome>(),
|
|
new ReadTagValuesFailure(ReadTagValuesFailureKind.ServerError, ex.Message));
|
|
}
|
|
}
|
|
}
|