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 @@
}
+
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]