@* Create / edit modal for a raw-tree Device's connection endpoint (v3 endpoint→DeviceConfig split). Dispatches to the right per-driver device form by DriverType. Test-connect builds the merged Driver+Device config transiently (so unsaved edits probe correctly), via DriverDeviceConfigMerger — the same merge LoadMergedProbeConfigAsync uses. The coordinator wires this into RawTree via @bind-Visible. *@ @using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.DeviceForms @using ZB.MOM.WW.OtOpcUa.AdminUI.Uns @using ZB.MOM.WW.OtOpcUa.Commons.Types @using ZB.MOM.WW.OtOpcUa.Core.Abstractions @inject IRawTreeService Svc @if (Visible) { } @code { /// Whether the modal is shown (supports @bind-Visible). [Parameter] public bool Visible { get; set; } /// Two-way visibility callback. [Parameter] public EventCallback VisibleChanged { get; set; } /// The device to edit; null ⇒ create a new device under . [Parameter] public string? DeviceId { get; set; } /// The owning driver instance (required for create; resolved from the device on edit). [Parameter] public string? DriverInstanceId { get; set; } /// The driver type (required for create; joined in from the device on edit). [Parameter] public string? DriverType { get; set; } /// Raised after a successful create/update so the tree can refresh. [Parameter] public EventCallback OnSaved { get; set; } private bool _isNew; private string _driverType = ""; private string? _resolvedDriverInstanceId; private string _name = ""; private bool _enabled = true; private string _deviceConfigJson = "{}"; private byte[] _rowVersion = []; private string _parentDriverConfig = "{}"; private bool _busy; private string? _loadError; private string? _saveError; private bool _openHandled; protected override async Task OnParametersSetAsync() { if (Visible && !_openHandled) { _openHandled = true; await LoadAsync(); } else if (!Visible) { _openHandled = false; } } private async Task LoadAsync() { _busy = false; _loadError = null; _saveError = null; _isNew = string.IsNullOrEmpty(DeviceId); if (_isNew) { _resolvedDriverInstanceId = DriverInstanceId; _driverType = DriverType ?? ""; _name = "Device1"; _enabled = true; _deviceConfigJson = "{}"; _rowVersion = []; } else { var dto = await Svc.LoadDeviceForEditAsync(DeviceId!); if (dto is null) { _loadError = $"Device '{DeviceId}' was not found."; return; } _resolvedDriverInstanceId = dto.DriverInstanceId; _driverType = dto.DriverType; _name = dto.Name; _enabled = dto.Enabled; _deviceConfigJson = string.IsNullOrWhiteSpace(dto.DeviceConfig) ? "{}" : dto.DeviceConfig; _rowVersion = dto.RowVersion; } // The parent driver's DriverConfig — folded with the (possibly unsaved) DeviceConfig for Test-connect. if (!string.IsNullOrEmpty(_resolvedDriverInstanceId)) { var drv = await Svc.LoadDriverForEditAsync(_resolvedDriverInstanceId); _parentDriverConfig = drv?.DriverConfig ?? "{}"; } } /// Builds the merged Driver+Device probe config from the CURRENT (possibly unsaved) form — /// same merge as LoadMergedProbeConfigAsync, so Test-connect works before the device is saved. private string BuildMergedProbeConfig() => DriverDeviceConfigMerger.Merge( _parentDriverConfig, new[] { new DriverDeviceConfigMerger.DeviceRow(_name, _deviceConfigJson) }, Array.Empty()); private async Task SaveAsync() { _busy = true; _saveError = null; try { UnsMutationResult result; if (_isNew) { if (string.IsNullOrEmpty(_resolvedDriverInstanceId)) { _saveError = "No owning driver instance was supplied."; return; } result = await Svc.CreateDeviceAsync(_resolvedDriverInstanceId, _name, _deviceConfigJson); } else { result = await Svc.UpdateDeviceAsync(DeviceId!, _name, _deviceConfigJson, _enabled, _rowVersion); } if (!result.Ok) { _saveError = result.Error ?? "Save failed."; return; } await OnSaved.InvokeAsync(); await SetVisibleAsync(false); } catch (Exception ex) { _saveError = ex.Message; } finally { _busy = false; } } private Task CloseAsync() => SetVisibleAsync(false); private async Task SetVisibleAsync(bool value) { Visible = value; _openHandled = value; await VisibleChanged.InvokeAsync(value); } }