diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/DataConnectionForm.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/DataConnectionForm.razor index 3d8314fe..a96df290 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/DataConnectionForm.razor +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/DataConnectionForm.razor @@ -49,17 +49,45 @@ } +
+ + @if (_protocolLocked) + { + +
Protocol is locked after creation.
+ } + else + { + + } +
Primary endpoint
- + @if (_protocol == "MxGateway") + { + + } + else + { + + }
Backup endpoint @@ -77,11 +105,21 @@ } else { - + @if (_protocol == "MxGateway") + { + + } + else + { + + }
s.Id == _formSiteId)?.Name ?? $"Site {_formSiteId}"; _siteLocked = true; _formName = _editingConnection.Name; + _protocol = string.IsNullOrWhiteSpace(_editingConnection.Protocol) ? "OpcUa" : _editingConnection.Protocol; + _protocolLocked = true; - (_primaryConfig, _primaryIsLegacy) = - OpcUaEndpointConfigSerializer.Deserialize(_editingConnection.PrimaryConfiguration); - - if (!string.IsNullOrWhiteSpace(_editingConnection.BackupConfiguration)) - { - (_backupConfig, _backupIsLegacy) = - OpcUaEndpointConfigSerializer.Deserialize(_editingConnection.BackupConfiguration); - _showBackup = true; - _formFailoverRetryCount = _editingConnection.FailoverRetryCount; - } + LoadConfig(_editingConnection); } } else if (SiteId.HasValue) @@ -177,32 +212,80 @@ } } + 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; } - _primaryErrors = OpcUaEndpointConfigValidator.Validate(_primaryConfig, "Primary."); - _backupErrors = _showBackup - ? OpcUaEndpointConfigValidator.Validate(_backupConfig, "Backup.") - : null; + string primaryJson; + string? backupJson; - if (!_primaryErrors.IsValid || (_backupErrors is { IsValid: false })) + if (_protocol == "MxGateway") { - _formError = "Fix the errors below before saving."; - return; - } + _primaryErrors = MxGatewayEndpointConfigValidator.Validate(_primaryMx, "Primary."); + _backupErrors = _showBackup + ? MxGatewayEndpointConfigValidator.Validate(_backupMx, "Backup.") + : null; - var primaryJson = OpcUaEndpointConfigSerializer.Serialize(_primaryConfig); - var backupJson = _showBackup ? OpcUaEndpointConfigSerializer.Serialize(_backupConfig) : 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 = "OpcUa"; + _editingConnection.Protocol = _protocol; _editingConnection.PrimaryConfiguration = primaryJson; _editingConnection.BackupConfiguration = backupJson; _editingConnection.FailoverRetryCount = _showBackup ? _formFailoverRetryCount : 3; @@ -210,7 +293,7 @@ } else { - var conn = new DataConnection(_formName.Trim(), "OpcUa", _formSiteId) + var conn = new DataConnection(_formName.Trim(), _protocol, _formSiteId) { PrimaryConfiguration = primaryJson, BackupConfiguration = backupJson, @@ -233,6 +316,7 @@ { _showBackup = false; _backupConfig = new OpcUaEndpointConfig(); + _backupMx = new MxGatewayEndpointConfig(); _backupIsLegacy = false; _formFailoverRetryCount = 3; } diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/DataConnectionFormTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/DataConnectionFormTests.cs index b9d378b2..aaac4756 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/DataConnectionFormTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/DataConnectionFormTests.cs @@ -44,12 +44,55 @@ public class DataConnectionFormTests : BunitContext } [Fact] - public void NoProtocolDropdown_IsRendered() + public void ProtocolDropdown_IsRendered_OnCreate_WithBothProtocols() { var cut = RenderForCreateSite(1); - Assert.DoesNotContain("Custom", cut.Markup); var labels = cut.FindAll("label").Select(l => l.TextContent.Trim()).ToList(); - Assert.DoesNotContain(labels, l => l == "Protocol"); + Assert.Contains(labels, l => l == "Protocol"); + + // The protocol select offers OPC UA and MxGateway. + var optionTexts = cut.FindAll("option").Select(o => o.TextContent.Trim()).ToList(); + Assert.Contains("OPC UA", optionTexts); + Assert.Contains("MxGateway", optionTexts); + } + + [Fact] + public async Task Save_MxGateway_PersistsTypedJsonAndProtocolMxGateway() + { + DataConnection? captured = null; + await _siteRepo.AddDataConnectionAsync( + Arg.Do(d => captured = d)); + + var cut = RenderForCreateSite(1); + + // Switch protocol to MxGateway — re-renders with the MxGateway editor. + cut.FindAll("select") + .First(s => s.QuerySelectorAll("option").Any(o => o.TextContent.Trim() == "MxGateway")) + .Change("MxGateway"); + + // Name (skip readonly Site plaintext input; MxGateway editor inputs carry placeholders). + cut.FindAll("input[type='text']") + .First(i => !i.HasAttribute("readonly") && i.GetAttribute("placeholder") is null) + .Change("MX-1"); + // Gateway endpoint + cut.FindAll("input[type='text']") + .First(i => i.GetAttribute("placeholder")?.StartsWith("http://") == true) + .Change("http://gw:5000"); + // API key (password input) + cut.FindAll("input[type='password']") + .First(i => i.GetAttribute("placeholder")?.Contains("API key") == true) + .Change("secret"); + + await cut.FindAll("button") + .First(b => b.TextContent.Trim() == "Save").ClickAsync(new()); + + Assert.NotNull(captured); + Assert.Equal("MxGateway", captured!.Protocol); + Assert.NotNull(captured.PrimaryConfiguration); + + using var doc = JsonDocument.Parse(captured.PrimaryConfiguration!); + Assert.Equal("http://gw:5000", + doc.RootElement.GetProperty("endpoint").GetString()); } [Fact]