diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs index cbdf34d..12e5e40 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs @@ -1299,6 +1299,434 @@ public sealed class SonicSetupRepository return Convert.ToInt32(result); } + public async Task UpdateSonicConnectionAsync( + int sonicConnectionId, + string connectionName, + string managementHost, + int managementPort, + string domainName, + string connectionProtocol, + CancellationToken cancellationToken = default) + { + if (sonicConnectionId <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(sonicConnectionId)); + } + + string name = RequireTrimmed( + connectionName, + 150, + "ConnectionName"); + + string host = RequireTrimmed( + managementHost, + 255, + "ManagementHost"); + + string domain = RequireTrimmed( + domainName, + 150, + "DomainName"); + + string protocol = string.IsNullOrWhiteSpace(connectionProtocol) + ? "tcp" + : connectionProtocol.Trim().TrimEnd(':', '/'); + + if (managementPort is < 1 or > 65535) + { + throw new ArgumentOutOfRangeException( + nameof(managementPort), + "Der ManagementPort muss zwischen 1 und 65535 liegen."); + } + + const string sql = """ + UPDATE [dbo].[SonicConnection] + SET + [ConnectionName] = @ConnectionName, + [ManagementHost] = @ManagementHost, + [ManagementPort] = @ManagementPort, + [DomainName] = @DomainName, + [ConnectionProtocol] = @ConnectionProtocol, + [ModifiedDateTime] = SYSUTCDATETIME(), + [ModifiedBy] = SUSER_SNAME() + WHERE [SonicConnectionId] = @SonicConnectionId + AND [IsActive] = 1; + """; + + await using SqlConnection connection = + new(_connectionString); + + await connection.OpenAsync(cancellationToken); + + await using SqlCommand command = + new(sql, connection); + + command.Parameters.Add( + new SqlParameter("@SonicConnectionId", SqlDbType.Int) + { + Value = sonicConnectionId + }); + + command.Parameters.Add( + new SqlParameter( + "@ConnectionName", + SqlDbType.NVarChar, + 150) + { + Value = name + }); + + command.Parameters.Add( + new SqlParameter( + "@ManagementHost", + SqlDbType.NVarChar, + 255) + { + Value = host + }); + + command.Parameters.Add( + new SqlParameter("@ManagementPort", SqlDbType.Int) + { + Value = managementPort + }); + + command.Parameters.Add( + new SqlParameter( + "@DomainName", + SqlDbType.NVarChar, + 150) + { + Value = domain + }); + + command.Parameters.Add( + new SqlParameter( + "@ConnectionProtocol", + SqlDbType.NVarChar, + 20) + { + Value = protocol + }); + + int affected = + await command.ExecuteNonQueryAsync(cancellationToken); + + if (affected == 0) + { + throw new InvalidOperationException( + $"Sonic-System Id {sonicConnectionId} " + + "wurde nicht gefunden oder ist bereits inaktiv."); + } + } + + public async Task DeactivateSonicConnectionAsync( + int sonicConnectionId, + CancellationToken cancellationToken = default) + { + if (sonicConnectionId <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(sonicConnectionId)); + } + + const string sql = """ + UPDATE [dbo].[SonicConnection] + SET + [IsActive] = 0, + [ModifiedDateTime] = SYSUTCDATETIME(), + [ModifiedBy] = SUSER_SNAME() + WHERE [SonicConnectionId] = @SonicConnectionId + AND [IsActive] = 1; + """; + + await ExecuteSoftDeleteAsync( + sql, + "@SonicConnectionId", + sonicConnectionId, + $"Sonic-System Id {sonicConnectionId} " + + "wurde nicht gefunden oder ist bereits inaktiv.", + cancellationToken); + } + + public async Task UpdateSonicContainerAsync( + int sonicContainerId, + int companyId, + int sonicConnectionId, + string containerName, + string? containerDisplayName, + int restartTimeoutSeconds, + CancellationToken cancellationToken = default) + { + if (sonicContainerId <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(sonicContainerId)); + } + + if (companyId <= 0) + { + throw new ArgumentOutOfRangeException(nameof(companyId)); + } + + if (sonicConnectionId <= 0) + { + throw new ArgumentOutOfRangeException(nameof(sonicConnectionId)); + } + + string name = RequireTrimmed( + containerName, + 250, + "ContainerName"); + + string? display = + string.IsNullOrWhiteSpace(containerDisplayName) + ? null + : containerDisplayName.Trim(); + + if (display is { Length: > 250 }) + { + throw new ArgumentException( + "Der Anzeigename darf maximal 250 Zeichen enthalten.", + nameof(containerDisplayName)); + } + + if (restartTimeoutSeconds is < 10 or > 3600) + { + throw new ArgumentOutOfRangeException( + nameof(restartTimeoutSeconds), + "Timeout muss zwischen 10 und 3600 Sekunden liegen."); + } + + const string sql = """ + UPDATE [dbo].[SonicContainer] + SET + [CompanyId] = @CompanyId, + [SonicConnectionId] = @SonicConnectionId, + [ContainerName] = @ContainerName, + [ContainerDisplayName] = @ContainerDisplayName, + [RestartTimeoutSeconds] = @RestartTimeoutSeconds, + [ModifiedDateTime] = SYSUTCDATETIME(), + [ModifiedBy] = SUSER_SNAME() + WHERE [SonicContainerId] = @SonicContainerId + AND [IsActive] = 1; + """; + + await using SqlConnection connection = + new(_connectionString); + + await connection.OpenAsync(cancellationToken); + + await using SqlCommand command = + new(sql, connection); + + command.Parameters.Add( + new SqlParameter("@SonicContainerId", SqlDbType.Int) + { + Value = sonicContainerId + }); + + command.Parameters.Add( + new SqlParameter("@CompanyId", SqlDbType.Int) + { + Value = companyId + }); + + command.Parameters.Add( + new SqlParameter("@SonicConnectionId", SqlDbType.Int) + { + Value = sonicConnectionId + }); + + command.Parameters.Add( + new SqlParameter( + "@ContainerName", + SqlDbType.NVarChar, + 250) + { + Value = name + }); + + command.Parameters.Add( + new SqlParameter( + "@ContainerDisplayName", + SqlDbType.NVarChar, + 250) + { + Value = display is null + ? DBNull.Value + : display + }); + + command.Parameters.Add( + new SqlParameter( + "@RestartTimeoutSeconds", + SqlDbType.Int) + { + Value = restartTimeoutSeconds + }); + + int affected = + await command.ExecuteNonQueryAsync(cancellationToken); + + if (affected == 0) + { + throw new InvalidOperationException( + $"Container Id {sonicContainerId} " + + "wurde nicht gefunden oder ist bereits inaktiv."); + } + } + + public async Task DeactivateSonicContainerAsync( + int sonicContainerId, + CancellationToken cancellationToken = default) + { + if (sonicContainerId <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(sonicContainerId)); + } + + const string sql = """ + UPDATE [dbo].[SonicContainer] + SET + [IsActive] = 0, + [ModifiedDateTime] = SYSUTCDATETIME(), + [ModifiedBy] = SUSER_SNAME() + WHERE [SonicContainerId] = @SonicContainerId + AND [IsActive] = 1; + """; + + await ExecuteSoftDeleteAsync( + sql, + "@SonicContainerId", + sonicContainerId, + $"Container Id {sonicContainerId} " + + "wurde nicht gefunden oder ist bereits inaktiv.", + cancellationToken); + } + + public async Task UpdateCertificateTargetAsync( + int certificateTargetId, + int sonicContainerId, + string targetDirectory, + string targetFileName, + bool backupEnabled, + string backupDirectoryName, + CancellationToken cancellationToken = default) + { + if (certificateTargetId <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(certificateTargetId)); + } + + if (sonicContainerId <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(sonicContainerId)); + } + + string directory = RequireTrimmed( + targetDirectory, + 500, + "TargetDirectory"); + + string fileName = RequireTrimmed( + targetFileName, + 260, + "TargetFileName"); + + string backupName = string.IsNullOrWhiteSpace(backupDirectoryName) + ? "Backup" + : backupDirectoryName.Trim(); + + if (backupName.Length > 100) + { + throw new ArgumentException( + "Der Backup-Ordnername darf maximal 100 Zeichen enthalten.", + nameof(backupDirectoryName)); + } + + const string sql = """ + UPDATE [dbo].[CertificateTarget] + SET + [SonicContainerId] = @SonicContainerId, + [TargetDirectory] = @TargetDirectory, + [TargetFileName] = @TargetFileName, + [BackupEnabled] = @BackupEnabled, + [BackupDirectoryName] = @BackupDirectoryName, + [ModifiedDateTime] = SYSUTCDATETIME(), + [ModifiedBy] = SUSER_SNAME() + WHERE [CertificateTargetId] = @CertificateTargetId + AND [IsActive] = 1; + """; + + await using SqlConnection connection = + new(_connectionString); + + await connection.OpenAsync(cancellationToken); + + await using SqlCommand command = + new(sql, connection); + + command.Parameters.Add( + new SqlParameter("@CertificateTargetId", SqlDbType.Int) + { + Value = certificateTargetId + }); + + command.Parameters.Add( + new SqlParameter("@SonicContainerId", SqlDbType.Int) + { + Value = sonicContainerId + }); + + command.Parameters.Add( + new SqlParameter( + "@TargetDirectory", + SqlDbType.NVarChar, + 500) + { + Value = directory + }); + + command.Parameters.Add( + new SqlParameter( + "@TargetFileName", + SqlDbType.NVarChar, + 260) + { + Value = fileName + }); + + command.Parameters.Add( + new SqlParameter("@BackupEnabled", SqlDbType.Bit) + { + Value = backupEnabled + }); + + command.Parameters.Add( + new SqlParameter( + "@BackupDirectoryName", + SqlDbType.NVarChar, + 100) + { + Value = backupName + }); + + int affected = + await command.ExecuteNonQueryAsync(cancellationToken); + + if (affected == 0) + { + throw new InvalidOperationException( + $"Zertifikat-Pfad Id {certificateTargetId} " + + "wurde nicht gefunden oder ist bereits inaktiv."); + } + } + public async Task DeactivateCertificateTargetAsync( int certificateTargetId, CancellationToken cancellationToken = default) @@ -1319,6 +1747,22 @@ public sealed class SonicSetupRepository AND [IsActive] = 1; """; + await ExecuteSoftDeleteAsync( + sql, + "@CertificateTargetId", + certificateTargetId, + $"Zertifikat-Pfad Id {certificateTargetId} " + + "wurde nicht gefunden oder ist bereits inaktiv.", + cancellationToken); + } + + private async Task ExecuteSoftDeleteAsync( + string sql, + string idParameterName, + int id, + string notFoundMessage, + CancellationToken cancellationToken) + { await using SqlConnection connection = new(_connectionString); @@ -1328,11 +1772,9 @@ public sealed class SonicSetupRepository new(sql, connection); command.Parameters.Add( - new SqlParameter( - "@CertificateTargetId", - SqlDbType.Int) + new SqlParameter(idParameterName, SqlDbType.Int) { - Value = certificateTargetId + Value = id }); int affected = @@ -1340,9 +1782,7 @@ public sealed class SonicSetupRepository if (affected == 0) { - throw new InvalidOperationException( - $"Zertifikat-Pfad Id {certificateTargetId} " - + "wurde nicht gefunden oder ist bereits inaktiv."); + throw new InvalidOperationException(notFoundMessage); } } diff --git a/ZA.CoreService.ESBCertificateManager/Setup/AddContainerForm.cs b/ZA.CoreService.ESBCertificateManager/Setup/AddContainerForm.cs index 68a528b..e32c353 100644 --- a/ZA.CoreService.ESBCertificateManager/Setup/AddContainerForm.cs +++ b/ZA.CoreService.ESBCertificateManager/Setup/AddContainerForm.cs @@ -5,6 +5,8 @@ namespace ZA.CoreService.ESBCertificateManager.Setup; public sealed class AddContainerForm : Form { private readonly SetupCoordinator _coordinator; + private readonly ContainerOption? _existing; + private readonly int? _preferredSonicConnectionId; private readonly ComboBox _cmbCompany; private readonly ComboBox _cmbSystem; private readonly TextBox _txtName; @@ -15,13 +17,20 @@ public sealed class AddContainerForm : Form public int? CreatedSonicContainerId { get; private set; } + public bool IsEditMode => _existing is not null; + public AddContainerForm( SetupCoordinator coordinator, - int? preferredSonicConnectionId = null) + int? preferredSonicConnectionId = null, + ContainerOption? existing = null) { _coordinator = coordinator; + _preferredSonicConnectionId = preferredSonicConnectionId; + _existing = existing; - Text = "Container hinzufügen"; + Text = IsEditMode + ? "Container bearbeiten" + : "Container hinzufügen"; StartPosition = FormStartPosition.CenterParent; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; @@ -34,7 +43,9 @@ public sealed class AddContainerForm : Form Label title = new() { - Text = "Neuer Container", + Text = IsEditMode + ? "Container bearbeiten" + : "Neuer Container", Location = new Point(28, 22), AutoSize = true, Font = new Font("Segoe UI Semibold", 18f, FontStyle.Bold), @@ -62,13 +73,21 @@ public sealed class AddContainerForm : Form _txtTimeout = SettingsUi.CreateTextBox(); _txtTimeout.Text = "180"; + if (_existing is not null) + { + _txtName.Text = _existing.ContainerName; + _txtDisplay.Text = _existing.ContainerDisplayName ?? string.Empty; + _txtTimeout.Text = _existing.RestartTimeoutSeconds.ToString(); + } + AddField(card, "Company", _cmbCompany, 18, 18, 440); AddField(card, "Sonic-System", _cmbSystem, 18, 78, 440); AddField(card, "Container-Name", _txtName, 18, 138, 440); AddField(card, "Anzeigename (optional)", _txtDisplay, 18, 198, 280); AddField(card, "Timeout (s)", _txtTimeout, 318, 198, 140); - _btnSave = SettingsUi.CreatePrimaryButton("Container anlegen"); + _btnSave = SettingsUi.CreatePrimaryButton( + IsEditMode ? "Änderungen speichern" : "Container anlegen"); _btnSave.Location = new Point(292, 405); _btnSave.Size = new Size(220, 40); _btnSave.Click += async (_, _) => await SaveAsync(); @@ -88,11 +107,10 @@ public sealed class AddContainerForm : Form Controls.Add(cancel); Controls.Add(_btnSave); - Shown += async (_, _) => - await LoadLookupsAsync(preferredSonicConnectionId); + Shown += async (_, _) => await LoadLookupsAsync(); } - private async Task LoadLookupsAsync(int? preferredSonicConnectionId) + private async Task LoadLookupsAsync() { try { @@ -103,33 +121,43 @@ public sealed class AddContainerForm : Form await _coordinator.LoadSystemsAsync(); _cmbCompany.Items.Clear(); - foreach (CompanyOption company in companies) + int companySelected = 0; + + for (int index = 0; index < companies.Count; index++) { - _cmbCompany.Items.Add(company); + _cmbCompany.Items.Add(companies[index]); + + if (_existing is not null + && companies[index].CompanyId == _existing.CompanyId) + { + companySelected = index; + } } if (_cmbCompany.Items.Count > 0) { - _cmbCompany.SelectedIndex = 0; + _cmbCompany.SelectedIndex = companySelected; } _cmbSystem.Items.Clear(); - int selected = 0; + int systemSelected = 0; + int? preferredSystemId = + _existing?.SonicConnectionId ?? _preferredSonicConnectionId; for (int index = 0; index < systems.Count; index++) { _cmbSystem.Items.Add(systems[index]); - if (preferredSonicConnectionId is int id + if (preferredSystemId is int id && systems[index].SonicConnectionId == id) { - selected = index; + systemSelected = index; } } if (_cmbSystem.Items.Count > 0) { - _cmbSystem.SelectedIndex = selected; + _cmbSystem.SelectedIndex = systemSelected; } } catch (Exception ex) @@ -189,14 +217,29 @@ public sealed class AddContainerForm : Form try { - CreatedSonicContainerId = - await _coordinator.AddSonicContainerAsync( + if (_existing is not null) + { + await _coordinator.UpdateSonicContainerAsync( + _existing.SonicContainerId, company.CompanyId, system.SonicConnectionId, _txtName.Text, _txtDisplay.Text, timeout); + CreatedSonicContainerId = _existing.SonicContainerId; + } + else + { + CreatedSonicContainerId = + await _coordinator.AddSonicContainerAsync( + company.CompanyId, + system.SonicConnectionId, + _txtName.Text, + _txtDisplay.Text, + timeout); + } + DialogResult = DialogResult.OK; Close(); } diff --git a/ZA.CoreService.ESBCertificateManager/Setup/AddSonicSystemForm.cs b/ZA.CoreService.ESBCertificateManager/Setup/AddSonicSystemForm.cs index 6cf2704..9b64e65 100644 --- a/ZA.CoreService.ESBCertificateManager/Setup/AddSonicSystemForm.cs +++ b/ZA.CoreService.ESBCertificateManager/Setup/AddSonicSystemForm.cs @@ -1,8 +1,11 @@ +using ZA.CoreService.ESBCertificateManager.Models; + namespace ZA.CoreService.ESBCertificateManager.Setup; public sealed class AddSonicSystemForm : Form { private readonly SetupCoordinator _coordinator; + private readonly SonicSystemOption? _existing; private readonly TextBox _txtName; private readonly TextBox _txtHost; private readonly TextBox _txtPort; @@ -13,11 +16,18 @@ public sealed class AddSonicSystemForm : Form public int? CreatedSonicConnectionId { get; private set; } - public AddSonicSystemForm(SetupCoordinator coordinator) + public bool IsEditMode => _existing is not null; + + public AddSonicSystemForm( + SetupCoordinator coordinator, + SonicSystemOption? existing = null) { _coordinator = coordinator; + _existing = existing; - Text = "Sonic-System hinzufügen"; + Text = IsEditMode + ? "Sonic-System bearbeiten" + : "Sonic-System hinzufügen"; StartPosition = FormStartPosition.CenterParent; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; @@ -30,7 +40,9 @@ public sealed class AddSonicSystemForm : Form Label title = new() { - Text = "Neues Sonic-System", + Text = IsEditMode + ? "Sonic-System bearbeiten" + : "Neues Sonic-System", Location = new Point(28, 22), AutoSize = true, Font = new Font("Segoe UI Semibold", 18f, FontStyle.Bold), @@ -61,13 +73,30 @@ public sealed class AddSonicSystemForm : Form _cmbProtocol.SelectedIndex = 0; _txtPort.Text = "13070"; + if (_existing is not null) + { + _txtName.Text = _existing.ConnectionName; + _txtHost.Text = _existing.ManagementHost; + _txtPort.Text = _existing.ManagementPort.ToString(); + _txtDomain.Text = _existing.DomainName; + + string protocol = _existing.ConnectionProtocol + .Trim() + .TrimEnd(':', '/') + .ToLowerInvariant(); + + int protocolIndex = _cmbProtocol.Items.IndexOf(protocol); + _cmbProtocol.SelectedIndex = protocolIndex >= 0 ? protocolIndex : 0; + } + AddField(card, "Verbindungsname", _txtName, 18, 18, 420); AddField(card, "Management-Host", _txtHost, 18, 78, 280); AddField(card, "Port", _txtPort, 316, 78, 122); AddField(card, "Domain", _txtDomain, 18, 138, 420); AddField(card, "Protokoll", _cmbProtocol, 18, 198, 160); - _btnSave = SettingsUi.CreatePrimaryButton("System anlegen"); + _btnSave = SettingsUi.CreatePrimaryButton( + IsEditMode ? "Änderungen speichern" : "System anlegen"); _btnSave.Location = new Point(292, 365); _btnSave.Size = new Size(200, 40); _btnSave.Click += async (_, _) => await SaveAsync(); @@ -114,13 +143,31 @@ public sealed class AddSonicSystemForm : Form try { - CreatedSonicConnectionId = - await _coordinator.AddSonicConnectionAsync( + string protocol = + _cmbProtocol.SelectedItem?.ToString() ?? "tcp"; + + if (_existing is not null) + { + await _coordinator.UpdateSonicConnectionAsync( + _existing.SonicConnectionId, _txtName.Text, _txtHost.Text, port, _txtDomain.Text, - _cmbProtocol.SelectedItem?.ToString() ?? "tcp"); + protocol); + + CreatedSonicConnectionId = _existing.SonicConnectionId; + } + else + { + CreatedSonicConnectionId = + await _coordinator.AddSonicConnectionAsync( + _txtName.Text, + _txtHost.Text, + port, + _txtDomain.Text, + protocol); + } DialogResult = DialogResult.OK; Close(); diff --git a/ZA.CoreService.ESBCertificateManager/Setup/AddTargetPathForm.cs b/ZA.CoreService.ESBCertificateManager/Setup/AddTargetPathForm.cs index 565b039..ab1e48f 100644 --- a/ZA.CoreService.ESBCertificateManager/Setup/AddTargetPathForm.cs +++ b/ZA.CoreService.ESBCertificateManager/Setup/AddTargetPathForm.cs @@ -7,6 +7,8 @@ public sealed class AddTargetPathForm : Form { private readonly SetupCoordinator _coordinator; private readonly CertificateProbeService _probeService = new(); + private readonly CertificateTargetOption? _existing; + private readonly int? _preferredSonicContainerId; private readonly ComboBox _cmbContainer; private readonly TextBox _txtFullPath; @@ -21,13 +23,20 @@ public sealed class AddTargetPathForm : Form public int? CreatedCertificateTargetId { get; private set; } + public bool IsEditMode => _existing is not null; + public AddTargetPathForm( SetupCoordinator coordinator, - int? preferredSonicContainerId = null) + int? preferredSonicContainerId = null, + CertificateTargetOption? existing = null) { _coordinator = coordinator; + _preferredSonicContainerId = preferredSonicContainerId; + _existing = existing; - Text = "Zertifikat-Pfad hinzufügen"; + Text = IsEditMode + ? "Zertifikat-Pfad bearbeiten" + : "Zertifikat-Pfad hinzufügen"; StartPosition = FormStartPosition.CenterParent; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; @@ -40,7 +49,9 @@ public sealed class AddTargetPathForm : Form Label title = new() { - Text = "Zertifikat-Datei-Pfad", + Text = IsEditMode + ? "Zertifikat-Pfad bearbeiten" + : "Zertifikat-Datei-Pfad", Location = new Point(28, 18), AutoSize = true, Font = new Font("Segoe UI Semibold", 18f, FontStyle.Bold), @@ -64,7 +75,8 @@ public sealed class AddTargetPathForm : Form card.Paint += SettingsUi.PaintCardBorder; _txtFullPath = SettingsUi.CreateTextBox(); - _txtFullPath.Text = DemoCertificatePath.FullPath; + _txtFullPath.Text = _existing?.FullTargetPath + ?? DemoCertificatePath.FullPath; _txtFullPath.PlaceholderText = DemoCertificatePath.FullPath; Button browse = SettingsUi.CreateGhostButton("…"); @@ -88,13 +100,13 @@ public sealed class AddTargetPathForm : Form _cmbContainer = SettingsUi.CreateComboBox(); _txtBackupFolder = SettingsUi.CreateTextBox(); - _txtBackupFolder.Text = "Backup"; + _txtBackupFolder.Text = _existing?.BackupDirectoryName ?? "Backup"; _chkBackup = new CheckBox { Text = "Backup vor dem Überschreiben", AutoSize = true, - Checked = true, + Checked = _existing?.BackupEnabled ?? true, ForeColor = SettingsUi.Text, Font = new Font("Segoe UI", 9.5f) }; @@ -140,7 +152,8 @@ public sealed class AddTargetPathForm : Form card.Controls.Add(_cmbContainer); card.Controls.Add(hint); - _btnSave = SettingsUi.CreatePrimaryButton("Pfad anlegen"); + _btnSave = SettingsUi.CreatePrimaryButton( + IsEditMode ? "Änderungen speichern" : "Pfad anlegen"); _btnSave.Location = new Point(392, 490); _btnSave.Size = new Size(220, 40); _btnSave.Click += async (_, _) => await SaveAsync(); @@ -160,8 +173,7 @@ public sealed class AddTargetPathForm : Form Controls.Add(cancel); Controls.Add(_btnSave); - Shown += async (_, _) => - await LoadContainersAsync(preferredSonicContainerId); + Shown += async (_, _) => await LoadContainersAsync(); } private void BrowseForFile() @@ -222,7 +234,7 @@ public sealed class AddTargetPathForm : Form + $"Ablauf: {expiry} · Fingerprint: {cert.FingerprintSha256}"; } - private async Task LoadContainersAsync(int? preferredSonicContainerId) + private async Task LoadContainersAsync() { try { @@ -231,12 +243,14 @@ public sealed class AddTargetPathForm : Form _cmbContainer.Items.Clear(); int selected = 0; + int? preferredId = + _existing?.SonicContainerId ?? _preferredSonicContainerId; for (int index = 0; index < containers.Count; index++) { _cmbContainer.Items.Add(containers[index]); - if (preferredSonicContainerId is int id + if (preferredId is int id && containers[index].SonicContainerId == id) { selected = index; @@ -329,14 +343,29 @@ public sealed class AddTargetPathForm : Form try { - CreatedCertificateTargetId = - await _coordinator.AddCertificateTargetAsync( + if (_existing is not null) + { + await _coordinator.UpdateCertificateTargetAsync( + _existing.CertificateTargetId, container.SonicContainerId, directory, fileName, _chkBackup.Checked, _txtBackupFolder.Text); + CreatedCertificateTargetId = _existing.CertificateTargetId; + } + else + { + CreatedCertificateTargetId = + await _coordinator.AddCertificateTargetAsync( + container.SonicContainerId, + directory, + fileName, + _chkBackup.Checked, + _txtBackupFolder.Text); + } + DialogResult = DialogResult.OK; Close(); } diff --git a/ZA.CoreService.ESBCertificateManager/Setup/SetupCoordinator.cs b/ZA.CoreService.ESBCertificateManager/Setup/SetupCoordinator.cs index e04c6c6..f4b549e 100644 --- a/ZA.CoreService.ESBCertificateManager/Setup/SetupCoordinator.cs +++ b/ZA.CoreService.ESBCertificateManager/Setup/SetupCoordinator.cs @@ -466,6 +466,126 @@ public sealed class SetupCoordinator return id; } + public async Task UpdateSonicConnectionAsync( + int sonicConnectionId, + string connectionName, + string managementHost, + int managementPort, + string domainName, + string connectionProtocol, + CancellationToken cancellationToken = default) + { + await _setupRepository.UpdateSonicConnectionAsync( + sonicConnectionId, + connectionName, + managementHost, + managementPort, + domainName, + connectionProtocol, + cancellationToken); + + await EnsureExistsAsync( + () => _setupRepository.ExistsSonicConnectionAsync( + sonicConnectionId, + cancellationToken), + $"Sonic-System Id {sonicConnectionId} " + + "wurde aktualisiert, ist danach aber nicht auffindbar."); + } + + public async Task DeactivateSonicConnectionAsync( + int sonicConnectionId, + CancellationToken cancellationToken = default) + { + await _setupRepository.DeactivateSonicConnectionAsync( + sonicConnectionId, + cancellationToken); + + bool stillActive = + await _setupRepository.ExistsSonicConnectionAsync( + sonicConnectionId, + cancellationToken); + + if (stillActive) + { + throw new InvalidOperationException( + $"Sonic-System Id {sonicConnectionId} " + + "konnte nicht deaktiviert werden."); + } + } + + public async Task UpdateSonicContainerAsync( + int sonicContainerId, + int companyId, + int sonicConnectionId, + string containerName, + string? containerDisplayName, + int restartTimeoutSeconds, + CancellationToken cancellationToken = default) + { + await _setupRepository.UpdateSonicContainerAsync( + sonicContainerId, + companyId, + sonicConnectionId, + containerName, + containerDisplayName, + restartTimeoutSeconds, + cancellationToken); + + await EnsureExistsAsync( + () => _setupRepository.ExistsSonicContainerAsync( + sonicContainerId, + cancellationToken), + $"Container Id {sonicContainerId} " + + "wurde aktualisiert, ist danach aber nicht auffindbar."); + } + + public async Task DeactivateSonicContainerAsync( + int sonicContainerId, + CancellationToken cancellationToken = default) + { + await _setupRepository.DeactivateSonicContainerAsync( + sonicContainerId, + cancellationToken); + + bool stillActive = + await _setupRepository.ExistsSonicContainerAsync( + sonicContainerId, + cancellationToken); + + if (stillActive) + { + throw new InvalidOperationException( + $"Container Id {sonicContainerId} " + + "konnte nicht deaktiviert werden."); + } + } + + public async Task UpdateCertificateTargetAsync( + int certificateTargetId, + int sonicContainerId, + string targetDirectory, + string targetFileName, + bool backupEnabled, + string backupDirectoryName, + CancellationToken cancellationToken = default) + { + await _setupRepository.UpdateCertificateTargetAsync( + certificateTargetId, + sonicContainerId, + targetDirectory, + targetFileName, + backupEnabled, + backupDirectoryName, + cancellationToken); + + await EnsureExistsAsync( + () => _setupRepository.ExistsCertificateTargetAsync( + certificateTargetId, + cancellationToken), + $"Zertifikat-Pfad Id {certificateTargetId} " + + "wurde aktualisiert, ist danach aber nicht auffindbar."); + } + public async Task DeactivateCertificateTargetAsync( int certificateTargetId, CancellationToken cancellationToken = default) diff --git a/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs b/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs index f43e91c..897efdc 100644 --- a/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs +++ b/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs @@ -368,8 +368,9 @@ public sealed class SetupWizardForm : Form "Management-Verbindungen der aktuellen Umgebung."); Panel toolbar = CreateToolbar( - "+ System hinzufügen", - async () => await AddSystemAsync()); + ("+ System hinzufügen", async () => await AddSystemAsync(), true, 210), + ("Bearbeiten", async () => await EditSelectedSystemAsync(), false, 130), + ("Löschen", async () => await DeleteSelectedSystemAsync(), false, 120)); _lvSystems = SettingsUi.CreateListView(); _lvSystems.Dock = DockStyle.Fill; @@ -379,6 +380,7 @@ public sealed class SetupWizardForm : Form _lvSystems.Columns.Add("Domain", 180); _lvSystems.Columns.Add("Protokoll", 80); _lvSystems.Columns.Add("URL", 180); + _lvSystems.DoubleClick += async (_, _) => await EditSelectedSystemAsync(); FinishPanelLayout(_panelSystems, _lvSystems, toolbar); } @@ -390,8 +392,9 @@ public sealed class SetupWizardForm : Form "Sonic-Container je Company und System."); Panel toolbar = CreateToolbar( - "+ Container hinzufügen", - async () => await AddContainerAsync()); + ("+ Container hinzufügen", async () => await AddContainerAsync(), true, 220), + ("Bearbeiten", async () => await EditSelectedContainerAsync(), false, 130), + ("Löschen", async () => await DeleteSelectedContainerAsync(), false, 120)); _lvContainers = SettingsUi.CreateListView(); _lvContainers.Dock = DockStyle.Fill; @@ -400,6 +403,7 @@ public sealed class SetupWizardForm : Form _lvContainers.Columns.Add("Anzeige", 160); _lvContainers.Columns.Add("System", 160); _lvContainers.Columns.Add("Timeout", 90); + _lvContainers.DoubleClick += async (_, _) => await EditSelectedContainerAsync(); FinishPanelLayout(_panelContainers, _lvContainers, toolbar); } @@ -411,12 +415,10 @@ public sealed class SetupWizardForm : Form "Dateipfade (UNC/lokal) zum Austausch. Container = nur Neustart-Ziel."); Panel toolbar = CreateToolbar( - "+ Pfad hinzufügen", - async () => await AddPathAsync(), - "Pfad prüfen", - async () => await ProbeSelectedPathAsync(), - "Pfad löschen", - async () => await DeleteSelectedPathAsync()); + ("+ Pfad hinzufügen", async () => await AddPathAsync(), true, 190), + ("Bearbeiten", async () => await EditSelectedPathAsync(), false, 120), + ("Pfad prüfen", async () => await ProbeSelectedPathAsync(), false, 130), + ("Löschen", async () => await DeleteSelectedPathAsync(), false, 110)); _lvPaths = SettingsUi.CreateListView(); _lvPaths.Dock = DockStyle.Fill; @@ -426,17 +428,13 @@ public sealed class SetupWizardForm : Form _lvPaths.Columns.Add("Ablauf", 140); _lvPaths.Columns.Add("Backup", 80); _lvPaths.Columns.Add("System", 120); + _lvPaths.DoubleClick += async (_, _) => await EditSelectedPathAsync(); FinishPanelLayout(_panelPaths, _lvPaths, toolbar); } - private Panel CreateToolbar( - string addButtonText, - Func onAdd, - string? secondaryText = null, - Func? onSecondary = null, - string? tertiaryText = null, - Func? onTertiary = null) + private static Panel CreateToolbar( + params (string Text, Func Action, bool Primary, int Width)[] buttons) { Panel toolbar = new() { @@ -446,32 +444,23 @@ public sealed class SetupWizardForm : Form Padding = new Padding(8, 8, 8, 8) }; - Button add = SettingsUi.CreatePrimaryButton(addButtonText); - add.Dock = DockStyle.Left; - add.Width = 230; - add.Click += async (_, _) => await onAdd(); - - if (tertiaryText is not null && onTertiary is not null) + // Dock Left: zuletzt hinzugefügt erscheint ganz links + for (int index = buttons.Length - 1; index >= 0; index--) { - Button tertiary = SettingsUi.CreateGhostButton(tertiaryText); - tertiary.Dock = DockStyle.Left; - tertiary.Width = 140; - tertiary.Margin = new Padding(8, 0, 0, 0); - tertiary.Click += async (_, _) => await onTertiary(); - toolbar.Controls.Add(tertiary); + (string text, Func action, bool primary, int width) = + buttons[index]; + + Button button = primary + ? SettingsUi.CreatePrimaryButton(text) + : SettingsUi.CreateGhostButton(text); + + button.Dock = DockStyle.Left; + button.Width = width; + button.Margin = new Padding(8, 0, 0, 0); + button.Click += async (_, _) => await action(); + toolbar.Controls.Add(button); } - if (secondaryText is not null && onSecondary is not null) - { - Button secondary = SettingsUi.CreateGhostButton(secondaryText); - secondary.Dock = DockStyle.Left; - secondary.Width = 180; - secondary.Margin = new Padding(8, 0, 0, 0); - secondary.Click += async (_, _) => await onSecondary(); - toolbar.Controls.Add(secondary); - } - - toolbar.Controls.Add(add); return toolbar; } @@ -810,6 +799,90 @@ public sealed class SetupWizardForm : Form return; } + await AfterSystemSavedAsync(createdId, created: true); + } + + private async Task EditSelectedSystemAsync() + { + if (_lvSystems.SelectedItems.Count == 0 + || _lvSystems.SelectedItems[0].Tag is not SonicSystemOption system) + { + MessageBox.Show( + this, + "Bitte zuerst ein Sonic-System in der Liste wählen.", + "System bearbeiten", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + return; + } + + using AddSonicSystemForm dialog = new(_coordinator, system); + + if (dialog.ShowDialog(this) != DialogResult.OK + || dialog.CreatedSonicConnectionId is not int savedId) + { + return; + } + + await AfterSystemSavedAsync(savedId, created: false); + } + + private async Task DeleteSelectedSystemAsync() + { + if (_lvSystems.SelectedItems.Count == 0 + || _lvSystems.SelectedItems[0].Tag is not SonicSystemOption system) + { + MessageBox.Show( + this, + "Bitte zuerst ein Sonic-System in der Liste wählen.", + "System löschen", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + return; + } + + DialogResult confirm = MessageBox.Show( + this, + "Sonic-System wirklich aus der Konfiguration entfernen?\n\n" + + $"{system.ConnectionName}\n" + + $"{system.ConnectionUrl}\n\n" + + "Zugehörige Container und Pfade verschwinden aus den Listen. " + + "Der Eintrag wird in SQL nur deaktiviert (IsActive=0).", + "System löschen", + MessageBoxButtons.YesNo, + MessageBoxIcon.Warning); + + if (confirm != DialogResult.Yes) + { + return; + } + + try + { + await _coordinator.DeactivateSonicConnectionAsync( + system.SonicConnectionId); + + await LoadSystemsListAsync(); + await LoadContainersListAsync(); + await LoadPathsListAsync(); + await LoadSystemsAsync(); + await RefreshAllAsync(); + + MessageBox.Show( + this, + $"System Id {system.SonicConnectionId} wurde deaktiviert.", + "System gelöscht", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + catch (Exception ex) + { + ShowError(ex.Message); + } + } + + private async Task AfterSystemSavedAsync(int systemId, bool created) + { await LoadSystemsListAsync(); await LoadSystemsAsync(); await RefreshAllAsync(); @@ -818,13 +891,13 @@ public sealed class SetupWizardForm : Form .Cast() .Any(item => item.Tag is SonicSystemOption system - && system.SonicConnectionId == createdId); + && system.SonicConnectionId == systemId); if (!visible) { ShowError( - $"Anlage-Prüfung fehlgeschlagen: System-Id {createdId} " + - "ist nach dem Speichern nicht in der Liste."); + $"{(created ? "Anlage" : "Update")}-Prüfung fehlgeschlagen: " + + $"System-Id {systemId} ist nach dem Speichern nicht in der Liste."); return; } @@ -832,11 +905,13 @@ public sealed class SetupWizardForm : Form _lvSystems, item => item.Tag is SonicSystemOption system - && system.SonicConnectionId == createdId); + && system.SonicConnectionId == systemId); MessageBox.Show( this, - $"System wurde gespeichert und geprüft (Id {createdId}).", + created + ? $"System wurde gespeichert und geprüft (Id {systemId})." + : $"System wurde aktualisiert und geprüft (Id {systemId}).", "Prüfung OK", MessageBoxButtons.OK, MessageBoxIcon.Information); @@ -860,6 +935,96 @@ public sealed class SetupWizardForm : Form return; } + await AfterContainerSavedAsync(createdId, created: true); + } + + private async Task EditSelectedContainerAsync() + { + if (_lvContainers.SelectedItems.Count == 0 + || _lvContainers.SelectedItems[0].Tag is not ContainerOption container) + { + MessageBox.Show( + this, + "Bitte zuerst einen Container in der Liste wählen.", + "Container bearbeiten", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + return; + } + + using AddContainerForm dialog = new( + _coordinator, + container.SonicConnectionId, + container); + + if (dialog.ShowDialog(this) != DialogResult.OK + || dialog.CreatedSonicContainerId is not int savedId) + { + return; + } + + await AfterContainerSavedAsync(savedId, created: false); + } + + private async Task DeleteSelectedContainerAsync() + { + if (_lvContainers.SelectedItems.Count == 0 + || _lvContainers.SelectedItems[0].Tag is not ContainerOption container) + { + MessageBox.Show( + this, + "Bitte zuerst einen Container in der Liste wählen.", + "Container löschen", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + return; + } + + string display = + string.IsNullOrWhiteSpace(container.ContainerDisplayName) + ? container.ContainerName + : container.ContainerDisplayName; + + DialogResult confirm = MessageBox.Show( + this, + "Container wirklich aus der Konfiguration entfernen?\n\n" + + $"{container.CompanyCode} / {display}\n" + + $"System: {container.ConnectionName}\n\n" + + "Zugehörige Zertifikat-Pfade verschwinden aus der Liste. " + + "Der Eintrag wird in SQL nur deaktiviert (IsActive=0).", + "Container löschen", + MessageBoxButtons.YesNo, + MessageBoxIcon.Warning); + + if (confirm != DialogResult.Yes) + { + return; + } + + try + { + await _coordinator.DeactivateSonicContainerAsync( + container.SonicContainerId); + + await LoadContainersListAsync(); + await LoadPathsListAsync(); + await RefreshAllAsync(); + + MessageBox.Show( + this, + $"Container Id {container.SonicContainerId} wurde deaktiviert.", + "Container gelöscht", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + catch (Exception ex) + { + ShowError(ex.Message); + } + } + + private async Task AfterContainerSavedAsync(int containerId, bool created) + { await LoadContainersListAsync(); await LoadSystemsListAsync(); await RefreshAllAsync(); @@ -868,13 +1033,13 @@ public sealed class SetupWizardForm : Form .Cast() .Any(item => item.Tag is ContainerOption container - && container.SonicContainerId == createdId); + && container.SonicContainerId == containerId); if (!visible) { ShowError( - $"Anlage-Prüfung fehlgeschlagen: Container-Id {createdId} " + - "ist nach dem Speichern nicht in der Liste."); + $"{(created ? "Anlage" : "Update")}-Prüfung fehlgeschlagen: " + + $"Container-Id {containerId} ist nach dem Speichern nicht in der Liste."); return; } @@ -882,11 +1047,13 @@ public sealed class SetupWizardForm : Form _lvContainers, item => item.Tag is ContainerOption container - && container.SonicContainerId == createdId); + && container.SonicContainerId == containerId); MessageBox.Show( this, - $"Container wurde gespeichert und geprüft (Id {createdId}).", + created + ? $"Container wurde gespeichert und geprüft (Id {containerId})." + : $"Container wurde aktualisiert und geprüft (Id {containerId}).", "Prüfung OK", MessageBoxButtons.OK, MessageBoxIcon.Information); @@ -910,6 +1077,39 @@ public sealed class SetupWizardForm : Form return; } + await AfterPathSavedAsync(createdId, created: true); + } + + private async Task EditSelectedPathAsync() + { + if (_lvPaths.SelectedItems.Count == 0 + || _lvPaths.SelectedItems[0].Tag is not CertificateTargetOption target) + { + MessageBox.Show( + this, + "Bitte zuerst einen Zertifikat-Pfad in der Liste wählen.", + "Pfad bearbeiten", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + return; + } + + using AddTargetPathForm dialog = new( + _coordinator, + target.SonicContainerId, + target); + + if (dialog.ShowDialog(this) != DialogResult.OK + || dialog.CreatedCertificateTargetId is not int savedId) + { + return; + } + + await AfterPathSavedAsync(savedId, created: false); + } + + private async Task AfterPathSavedAsync(int pathId, bool created) + { await LoadPathsListAsync(); await RefreshAllAsync(); @@ -917,13 +1117,13 @@ public sealed class SetupWizardForm : Form .Cast() .Any(item => item.Tag is CertificateTargetOption target - && target.CertificateTargetId == createdId); + && target.CertificateTargetId == pathId); if (!visible) { ShowError( - $"Anlage-Prüfung fehlgeschlagen: Pfad-Id {createdId} " + - "ist nach dem Speichern nicht in der Liste."); + $"{(created ? "Anlage" : "Update")}-Prüfung fehlgeschlagen: " + + $"Pfad-Id {pathId} ist nach dem Speichern nicht in der Liste."); return; } @@ -931,11 +1131,13 @@ public sealed class SetupWizardForm : Form _lvPaths, item => item.Tag is CertificateTargetOption target - && target.CertificateTargetId == createdId); + && target.CertificateTargetId == pathId); MessageBox.Show( this, - $"Zertifikat-Pfad wurde gespeichert und geprüft (Id {createdId}).", + created + ? $"Zertifikat-Pfad wurde gespeichert und geprüft (Id {pathId})." + : $"Zertifikat-Pfad wurde aktualisiert und geprüft (Id {pathId}).", "Prüfung OK", MessageBoxButtons.OK, MessageBoxIcon.Information);