410349767d
T34c only fixed the bounded modal-surface offenders. Bootstrap 5.3's bg-light
and bg-white are fixed light values that do NOT flip under [data-bs-theme=dark],
so every remaining use was a dark-mode contrast break. 35 swaps across 19 files:
surface / <pre> / <code> bg-light -> bg-body-secondary
panel bg-white -> bg-body
neutral badge bg-light text-dark -> bg-secondary-subtle text-secondary-emphasis
muted badge / input group bg-light text-muted -> bg-body-secondary text-body-secondary
DELIBERATELY LEFT (7 sites): the neutral member of a status-badge switch or
ternary whose siblings are all solid, non-theme-aware colours (bg-success,
bg-danger, bg-warning) — swapping only the neutral one to a subtle token breaks
the visual weight of the set, so these stay until the whole family is retoned:
Topology.razor:513 (Current badge) and :588 (InstanceState.NotDeployed)
InstanceConfigure.razor:1520 (same InstanceState switch)
NotificationReport.razor StatusBadgeClass fallback
TransportImport.razor ConflictKind badge fallback
SecuredWrites.razor text-bg-light fallback (a text-bg-* family with no
theme-aware member at all)
Health.razor:377 depth ternary
Also untouched by design: SchemaBuilder.razor:88 (already bg-light-subtle,
theme-aware), bg-dark text-light console panels, and site.css / #reconnect-modal.
InstanceConfigure.razor is edited concurrently elsewhere; its change here is 5
pure class-string swaps on existing lines, no reflow. No test asserted any of
these classes. Also marks all four M10 residuals done in the plan doc.
354 lines
14 KiB
Plaintext
354 lines
14 KiB
Plaintext
@page "/design/connections/create"
|
|
@page "/design/connections/{Id:int}/edit"
|
|
@page "/design/data-connections/create"
|
|
@page "/design/data-connections/{Id:int}/edit"
|
|
@using ZB.MOM.WW.ScadaBridge.Security
|
|
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites
|
|
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories
|
|
@using ZB.MOM.WW.ScadaBridge.Commons.Types.DataConnections
|
|
@using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening
|
|
@using ZB.MOM.WW.ScadaBridge.Commons.Serialization
|
|
@using ZB.MOM.WW.ScadaBridge.Commons.Validators
|
|
@using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Forms
|
|
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDesign)]
|
|
@inject ISiteRepository SiteRepository
|
|
@inject NavigationManager NavigationManager
|
|
|
|
<div class="container-fluid mt-3">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<button class="btn btn-outline-secondary btn-sm me-3" @onclick="GoBack">← Back</button>
|
|
<h4 class="mb-0">@(Id.HasValue ? "Edit Data Connection" : "Add Data Connection")</h4>
|
|
</div>
|
|
|
|
@if (_loading)
|
|
{
|
|
<LoadingSpinner IsLoading="true" />
|
|
}
|
|
else
|
|
{
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
<div class="mb-2">
|
|
<label class="form-label small">Site</label>
|
|
@if (_siteLocked)
|
|
{
|
|
<input type="text"
|
|
class="form-control form-control-plaintext form-control-sm"
|
|
readonly
|
|
value="@_siteName" />
|
|
<div class="form-text">Site is locked after creation.</div>
|
|
}
|
|
else
|
|
{
|
|
<select class="form-select form-select-sm" @bind="_formSiteId">
|
|
<option value="0">Select site...</option>
|
|
@foreach (var site in _sites)
|
|
{
|
|
<option value="@site.Id">@site.Name</option>
|
|
}
|
|
</select>
|
|
}
|
|
</div>
|
|
<div class="mb-2">
|
|
<label class="form-label small">Protocol</label>
|
|
@if (_protocolLocked)
|
|
{
|
|
<input type="text"
|
|
class="form-control form-control-plaintext form-control-sm"
|
|
readonly
|
|
value="@_protocol" />
|
|
<div class="form-text">Protocol is locked after creation.</div>
|
|
}
|
|
else
|
|
{
|
|
<select class="form-select form-select-sm" @bind="_protocol">
|
|
<option value="OpcUa">OPC UA</option>
|
|
<option value="MxGateway">MxGateway</option>
|
|
</select>
|
|
}
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small">Name</label>
|
|
<input type="text" class="form-control form-control-sm" @bind="_formName" />
|
|
</div>
|
|
|
|
@if (Id.HasValue && _protocol == "OpcUa")
|
|
{
|
|
<div class="mb-2">
|
|
<a class="btn btn-outline-secondary btn-sm"
|
|
data-test="manage-certificates-link"
|
|
href="@($"/design/connections/{Id}/certificates")">
|
|
Manage certificates
|
|
</a>
|
|
<div class="form-text">
|
|
View, trust, and remove OPC UA server certificates in the
|
|
site's node-wide trusted-peer store.
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<h6 class="text-muted mt-3">Primary endpoint</h6>
|
|
@if (_protocol == "MxGateway")
|
|
{
|
|
<MxGatewayEndpointEditor Title="Primary Endpoint"
|
|
IdPrefix="primary"
|
|
Config="_primaryMx"
|
|
Errors="_primaryErrors" />
|
|
}
|
|
else
|
|
{
|
|
<OpcUaEndpointEditor Title="Primary Endpoint"
|
|
IdPrefix="primary"
|
|
Config="_primaryConfig"
|
|
IsLegacy="_primaryIsLegacy"
|
|
SiteIdentifier="@_formSiteIdentifier"
|
|
ConnectionName="@_formName"
|
|
Protocol="@_protocol"
|
|
Errors="_primaryErrors" />
|
|
}
|
|
|
|
<h6 class="text-muted mt-3">
|
|
Backup endpoint
|
|
@if (!_showBackup)
|
|
{
|
|
<span class="badge bg-body-secondary text-body-secondary border ms-2">Optional</span>
|
|
}
|
|
</h6>
|
|
@if (!_showBackup)
|
|
{
|
|
<div class="mb-3">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm"
|
|
@onclick="EnableBackup">Add Backup Endpoint</button>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
@if (_protocol == "MxGateway")
|
|
{
|
|
<MxGatewayEndpointEditor Title="Backup Endpoint"
|
|
IdPrefix="backup"
|
|
Config="_backupMx"
|
|
Errors="_backupErrors" />
|
|
}
|
|
else
|
|
{
|
|
<OpcUaEndpointEditor Title="Backup Endpoint"
|
|
IdPrefix="backup"
|
|
Config="_backupConfig"
|
|
IsLegacy="_backupIsLegacy"
|
|
SiteIdentifier="@_formSiteIdentifier"
|
|
ConnectionName="@_formName"
|
|
Protocol="@_protocol"
|
|
Errors="_backupErrors" />
|
|
}
|
|
<div class="mb-2">
|
|
<label class="form-label small">Failover Retry Count</label>
|
|
<input type="number" class="form-control form-control-sm" style="max-width: 120px;"
|
|
min="1" max="20" @bind="_formFailoverRetryCount" />
|
|
<div class="form-text">Retries before failing over to backup endpoint.</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<button type="button" class="btn btn-outline-danger btn-sm"
|
|
@onclick="RemoveBackup">Remove Backup</button>
|
|
</div>
|
|
}
|
|
|
|
@if (_formError != null)
|
|
{
|
|
<div class="text-danger small mt-2">@_formError</div>
|
|
}
|
|
<div class="mt-3">
|
|
<button class="btn btn-success btn-sm me-1" @onclick="SaveConnection">Save</button>
|
|
<button class="btn btn-outline-secondary btn-sm" @onclick="GoBack">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
[Parameter] public int? Id { get; set; }
|
|
[SupplyParameterFromQuery] public int? SiteId { get; set; }
|
|
|
|
private bool _loading = true;
|
|
private DataConnection? _editingConnection;
|
|
private List<Site> _sites = new();
|
|
private int _formSiteId;
|
|
private string _siteName = string.Empty;
|
|
private bool _siteLocked;
|
|
private string _formName = string.Empty;
|
|
private string _protocol = "OpcUa";
|
|
private bool _protocolLocked;
|
|
private OpcUaEndpointConfig _primaryConfig = new();
|
|
private OpcUaEndpointConfig _backupConfig = new();
|
|
private MxGatewayEndpointConfig _primaryMx = new();
|
|
private MxGatewayEndpointConfig _backupMx = new();
|
|
private bool _primaryIsLegacy;
|
|
private bool _backupIsLegacy;
|
|
private bool _showBackup;
|
|
private int _formFailoverRetryCount = 3;
|
|
private ValidationResult? _primaryErrors;
|
|
private ValidationResult? _backupErrors;
|
|
private string? _formError;
|
|
|
|
// The machine-readable site identifier (used in Akka addresses) for the currently
|
|
// selected site — resolved from _formSiteId (the numeric primary key) so the
|
|
// endpoint editor's Verify probe can target the owning site. Blank until a site is
|
|
// chosen, which disables verification in the editor.
|
|
private string _formSiteIdentifier =>
|
|
_sites.FirstOrDefault(s => s.Id == _formSiteId)?.SiteIdentifier ?? string.Empty;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
try
|
|
{
|
|
_sites = (await SiteRepository.GetAllSitesAsync()).ToList();
|
|
|
|
if (Id.HasValue)
|
|
{
|
|
_editingConnection = await SiteRepository.GetDataConnectionByIdAsync(Id.Value);
|
|
if (_editingConnection != null)
|
|
{
|
|
_formSiteId = _editingConnection.SiteId;
|
|
_siteName = _sites.FirstOrDefault(s => s.Id == _formSiteId)?.Name ?? $"Site {_formSiteId}";
|
|
_siteLocked = true;
|
|
_formName = _editingConnection.Name;
|
|
_protocol = string.IsNullOrWhiteSpace(_editingConnection.Protocol) ? "OpcUa" : _editingConnection.Protocol;
|
|
_protocolLocked = true;
|
|
|
|
LoadConfig(_editingConnection);
|
|
}
|
|
}
|
|
else if (SiteId.HasValue)
|
|
{
|
|
var site = _sites.FirstOrDefault(s => s.Id == SiteId.Value);
|
|
if (site != null)
|
|
{
|
|
_formSiteId = site.Id;
|
|
_siteName = site.Name;
|
|
_siteLocked = true;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_formError = $"Failed to load: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
_loading = false;
|
|
}
|
|
}
|
|
|
|
private void LoadConfig(DataConnection conn)
|
|
{
|
|
if (_protocol == "MxGateway")
|
|
{
|
|
_primaryMx = MxGatewayEndpointConfigSerializer.Deserialize(conn.PrimaryConfiguration);
|
|
if (!string.IsNullOrWhiteSpace(conn.BackupConfiguration))
|
|
{
|
|
_backupMx = MxGatewayEndpointConfigSerializer.Deserialize(conn.BackupConfiguration);
|
|
_showBackup = true;
|
|
_formFailoverRetryCount = conn.FailoverRetryCount;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
(_primaryConfig, _primaryIsLegacy) =
|
|
OpcUaEndpointConfigSerializer.Deserialize(conn.PrimaryConfiguration);
|
|
if (!string.IsNullOrWhiteSpace(conn.BackupConfiguration))
|
|
{
|
|
(_backupConfig, _backupIsLegacy) =
|
|
OpcUaEndpointConfigSerializer.Deserialize(conn.BackupConfiguration);
|
|
_showBackup = true;
|
|
_formFailoverRetryCount = conn.FailoverRetryCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task SaveConnection()
|
|
{
|
|
_formError = null;
|
|
if (_formSiteId == 0) { _formError = "Site is required."; return; }
|
|
if (string.IsNullOrWhiteSpace(_formName)) { _formError = "Name is required."; return; }
|
|
|
|
string primaryJson;
|
|
string? backupJson;
|
|
|
|
if (_protocol == "MxGateway")
|
|
{
|
|
_primaryErrors = MxGatewayEndpointConfigValidator.Validate(_primaryMx, "Primary.");
|
|
_backupErrors = _showBackup
|
|
? MxGatewayEndpointConfigValidator.Validate(_backupMx, "Backup.")
|
|
: null;
|
|
|
|
if (!_primaryErrors.IsValid || (_backupErrors is { IsValid: false }))
|
|
{
|
|
_formError = "Fix the errors below before saving.";
|
|
return;
|
|
}
|
|
|
|
primaryJson = MxGatewayEndpointConfigSerializer.Serialize(_primaryMx);
|
|
backupJson = _showBackup ? MxGatewayEndpointConfigSerializer.Serialize(_backupMx) : null;
|
|
}
|
|
else
|
|
{
|
|
_primaryErrors = OpcUaEndpointConfigValidator.Validate(_primaryConfig, "Primary.");
|
|
_backupErrors = _showBackup
|
|
? OpcUaEndpointConfigValidator.Validate(_backupConfig, "Backup.")
|
|
: null;
|
|
|
|
if (!_primaryErrors.IsValid || (_backupErrors is { IsValid: false }))
|
|
{
|
|
_formError = "Fix the errors below before saving.";
|
|
return;
|
|
}
|
|
|
|
primaryJson = OpcUaEndpointConfigSerializer.Serialize(_primaryConfig);
|
|
backupJson = _showBackup ? OpcUaEndpointConfigSerializer.Serialize(_backupConfig) : null;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (_editingConnection != null)
|
|
{
|
|
_editingConnection.Name = _formName.Trim();
|
|
_editingConnection.Protocol = _protocol;
|
|
_editingConnection.PrimaryConfiguration = primaryJson;
|
|
_editingConnection.BackupConfiguration = backupJson;
|
|
_editingConnection.FailoverRetryCount = _showBackup ? _formFailoverRetryCount : 3;
|
|
await SiteRepository.UpdateDataConnectionAsync(_editingConnection);
|
|
}
|
|
else
|
|
{
|
|
var conn = new DataConnection(_formName.Trim(), _protocol, _formSiteId)
|
|
{
|
|
PrimaryConfiguration = primaryJson,
|
|
BackupConfiguration = backupJson,
|
|
FailoverRetryCount = _showBackup ? _formFailoverRetryCount : 3
|
|
};
|
|
await SiteRepository.AddDataConnectionAsync(conn);
|
|
}
|
|
await SiteRepository.SaveChangesAsync();
|
|
NavigationManager.NavigateTo("/design/connections");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_formError = $"Save failed: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private void EnableBackup() => _showBackup = true;
|
|
|
|
private void RemoveBackup()
|
|
{
|
|
_showBackup = false;
|
|
_backupConfig = new OpcUaEndpointConfig();
|
|
_backupMx = new MxGatewayEndpointConfig();
|
|
_backupIsLegacy = false;
|
|
_formFailoverRetryCount = 3;
|
|
}
|
|
|
|
private void GoBack() => NavigationManager.NavigateTo("/design/connections");
|
|
}
|