diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs index 2298ee7..1330caa 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicSetupRepository.cs @@ -1299,6 +1299,81 @@ public sealed class SonicSetupRepository return Convert.ToInt32(result); } + public async Task ExistsSonicConnectionAsync( + int sonicConnectionId, + CancellationToken cancellationToken = default) + { + return await ExistsAsync( + """ + SELECT 1 + FROM [dbo].[SonicConnection] + WHERE [SonicConnectionId] = @Id + AND [IsActive] = 1; + """, + sonicConnectionId, + cancellationToken); + } + + public async Task ExistsSonicContainerAsync( + int sonicContainerId, + CancellationToken cancellationToken = default) + { + return await ExistsAsync( + """ + SELECT 1 + FROM [dbo].[SonicContainer] + WHERE [SonicContainerId] = @Id + AND [IsActive] = 1; + """, + sonicContainerId, + cancellationToken); + } + + public async Task ExistsCertificateTargetAsync( + int certificateTargetId, + CancellationToken cancellationToken = default) + { + return await ExistsAsync( + """ + SELECT 1 + FROM [dbo].[CertificateTarget] + WHERE [CertificateTargetId] = @Id + AND [IsActive] = 1; + """, + certificateTargetId, + cancellationToken); + } + + private async Task ExistsAsync( + string sql, + int id, + CancellationToken cancellationToken) + { + if (id <= 0) + { + return false; + } + + await using SqlConnection connection = + new(_connectionString); + + await connection.OpenAsync(cancellationToken); + + await using SqlCommand command = + new(sql, connection); + + command.Parameters.Add( + new SqlParameter("@Id", SqlDbType.Int) + { + Value = id + }); + + object? result = + await command.ExecuteScalarAsync(cancellationToken); + + return result is not null && result is not DBNull; + } + private static string RequireTrimmed( string? value, int maxLength, diff --git a/ZA.CoreService.ESBCertificateManager/Setup/SetupCoordinator.cs b/ZA.CoreService.ESBCertificateManager/Setup/SetupCoordinator.cs index d61f6a1..886f895 100644 --- a/ZA.CoreService.ESBCertificateManager/Setup/SetupCoordinator.cs +++ b/ZA.CoreService.ESBCertificateManager/Setup/SetupCoordinator.cs @@ -352,7 +352,7 @@ public sealed class SetupCoordinator cancellationToken); } - public Task AddSonicConnectionAsync( + public async Task AddSonicConnectionAsync( string connectionName, string managementHost, int managementPort, @@ -360,7 +360,7 @@ public sealed class SetupCoordinator string connectionProtocol, CancellationToken cancellationToken = default) { - return _setupRepository.AddSonicConnectionAsync( + int id = await _setupRepository.AddSonicConnectionAsync( _settings.EnvironmentCode, connectionName, managementHost, @@ -368,9 +368,29 @@ public sealed class SetupCoordinator domainName, connectionProtocol, cancellationToken); + + await EnsureExistsAsync( + () => _setupRepository.ExistsSonicConnectionAsync( + id, + cancellationToken), + $"Sonic-System wurde mit Id {id} geschrieben, " + + "ist danach in SQL aber nicht auffindbar."); + + IReadOnlyList systems = + await LoadSystemsAsync(cancellationToken); + + if (!systems.Any(system => system.SonicConnectionId == id)) + { + throw new InvalidOperationException( + $"Sonic-System Id {id} existiert in SQL, " + + $"erscheint aber nicht für Umgebung '{_settings.EnvironmentCode}'. " + + "EnvironmentCode in appsettings prüfen."); + } + + return id; } - public Task AddSonicContainerAsync( + public async Task AddSonicContainerAsync( int companyId, int sonicConnectionId, string containerName, @@ -378,16 +398,37 @@ public sealed class SetupCoordinator int restartTimeoutSeconds, CancellationToken cancellationToken = default) { - return _setupRepository.AddSonicContainerAsync( + int id = await _setupRepository.AddSonicContainerAsync( companyId, sonicConnectionId, containerName, containerDisplayName, restartTimeoutSeconds, cancellationToken); + + await EnsureExistsAsync( + () => _setupRepository.ExistsSonicContainerAsync( + id, + cancellationToken), + $"Container wurde mit Id {id} geschrieben, " + + "ist danach in SQL aber nicht auffindbar."); + + IReadOnlyList containers = + await LoadContainersAsync( + sonicConnectionId, + cancellationToken); + + if (!containers.Any(container => container.SonicContainerId == id)) + { + throw new InvalidOperationException( + $"Container Id {id} existiert in SQL, " + + "erscheint aber nicht in der Container-Liste."); + } + + return id; } - public Task AddCertificateTargetAsync( + public async Task AddCertificateTargetAsync( int sonicContainerId, string targetDirectory, string targetFileName, @@ -395,13 +436,44 @@ public sealed class SetupCoordinator string backupDirectoryName, CancellationToken cancellationToken = default) { - return _setupRepository.AddCertificateTargetAsync( + int id = await _setupRepository.AddCertificateTargetAsync( sonicContainerId, targetDirectory, targetFileName, backupEnabled, backupDirectoryName, cancellationToken); + + await EnsureExistsAsync( + () => _setupRepository.ExistsCertificateTargetAsync( + id, + cancellationToken), + $"Zertifikat-Pfad wurde mit Id {id} geschrieben, " + + "ist danach in SQL aber nicht auffindbar."); + + IReadOnlyList targets = + await LoadCertificateTargetsAsync( + sonicContainerId, + cancellationToken); + + if (!targets.Any(target => target.CertificateTargetId == id)) + { + throw new InvalidOperationException( + $"Zertifikat-Pfad Id {id} existiert in SQL, " + + "erscheint aber nicht in der Pfad-Liste."); + } + + return id; + } + + private static async Task EnsureExistsAsync( + Func> existsCheck, + string errorMessage) + { + if (!await existsCheck()) + { + throw new InvalidOperationException(errorMessage); + } } public void SaveSelection( diff --git a/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs b/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs index 8d3f1c8..d6aaebb 100644 --- a/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs +++ b/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs @@ -252,14 +252,32 @@ public sealed class SetupWizardForm : Form { Dock = DockStyle.Fill, BackColor = SettingsUi.Background, - Visible = false + Visible = false, + Padding = new Padding(4) + }; + + // Reihenfolge für Dock: Fill zuerst, dann Toolbar (Top), Header zuletzt (Top außen). + Panel header = CreateHeaderPanel(title, subtitle); + panel.Tag = header; + + _contentHost.Controls.Add(panel); + return panel; + } + + private static Panel CreateHeaderPanel(string title, string subtitle) + { + Panel header = new() + { + Dock = DockStyle.Top, + Height = 72, + BackColor = SettingsUi.Background }; Label heading = new() { Text = title, AutoSize = true, - Location = new Point(8, 4), + Location = new Point(4, 2), ForeColor = SettingsUi.Text, Font = new Font("Segoe UI Semibold", 22f, FontStyle.Bold) }; @@ -268,15 +286,24 @@ public sealed class SetupWizardForm : Form { Text = subtitle, AutoSize = true, - Location = new Point(12, 42), + Location = new Point(8, 40), ForeColor = SettingsUi.Muted, Font = new Font("Segoe UI", 10f) }; - panel.Controls.Add(heading); - panel.Controls.Add(sub); - _contentHost.Controls.Add(panel); - return panel; + header.Controls.Add(heading); + header.Controls.Add(sub); + return header; + } + + private static void FinishPanelLayout(Panel panel, Control list, Panel toolbar) + { + Panel header = (Panel)panel.Tag!; + + // 1) Fill 2) Toolbar Top 3) Header Top (außen) + panel.Controls.Add(list); + panel.Controls.Add(toolbar); + panel.Controls.Add(header); } private void BuildStatusPanel() @@ -286,9 +313,8 @@ public sealed class SetupWizardForm : Form "Runtime, Zertifikat, Datenbank und aktuelle Auswahl."); Panel card = SettingsUi.CreateCard(); - card.Location = new Point(8, 80); - card.Size = new Size(860, 280); - card.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + card.Dock = DockStyle.Fill; + card.Padding = new Padding(12); card.Paint += SettingsUi.PaintCardBorder; _lblRuntime = CreateStatusLabel(20, 24); @@ -319,7 +345,10 @@ public sealed class SetupWizardForm : Form card.Controls.Add(_lblSelection); card.Controls.Add(_btnImportCertificate); card.Controls.Add(tip); + + Panel header = (Panel)_panelStatus.Tag!; _panelStatus.Controls.Add(card); + _panelStatus.Controls.Add(header); } private void BuildSystemsPanel() @@ -328,13 +357,12 @@ public sealed class SetupWizardForm : Form "Sonic-Systeme", "Management-Verbindungen der aktuellen Umgebung."); - _lvSystems = SettingsUi.CreateListView(); - _lvSystems.Location = new Point(8, 80); - _lvSystems.Size = new Size(860, 420); - _lvSystems.Anchor = - AnchorStyles.Top | AnchorStyles.Bottom | - AnchorStyles.Left | AnchorStyles.Right; + Panel toolbar = CreateToolbar( + "+ System hinzufügen", + async () => await AddSystemAsync()); + _lvSystems = SettingsUi.CreateListView(); + _lvSystems.Dock = DockStyle.Fill; _lvSystems.Columns.Add("Name", 160); _lvSystems.Columns.Add("Host", 180); _lvSystems.Columns.Add("Port", 70); @@ -342,14 +370,7 @@ public sealed class SetupWizardForm : Form _lvSystems.Columns.Add("Protokoll", 80); _lvSystems.Columns.Add("URL", 180); - Button add = SettingsUi.CreatePrimaryButton("+ System hinzufügen"); - add.Location = new Point(8, 520); - add.Size = new Size(200, 40); - add.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; - add.Click += async (_, _) => await AddSystemAsync(); - - _panelSystems.Controls.Add(_lvSystems); - _panelSystems.Controls.Add(add); + FinishPanelLayout(_panelSystems, _lvSystems, toolbar); } private void BuildContainersPanel() @@ -358,27 +379,19 @@ public sealed class SetupWizardForm : Form "Container", "Sonic-Container je Company und System."); - _lvContainers = SettingsUi.CreateListView(); - _lvContainers.Location = new Point(8, 80); - _lvContainers.Size = new Size(860, 420); - _lvContainers.Anchor = - AnchorStyles.Top | AnchorStyles.Bottom | - AnchorStyles.Left | AnchorStyles.Right; + Panel toolbar = CreateToolbar( + "+ Container hinzufügen", + async () => await AddContainerAsync()); + _lvContainers = SettingsUi.CreateListView(); + _lvContainers.Dock = DockStyle.Fill; _lvContainers.Columns.Add("Company", 80); _lvContainers.Columns.Add("Container", 180); _lvContainers.Columns.Add("Anzeige", 160); _lvContainers.Columns.Add("System", 160); _lvContainers.Columns.Add("Timeout", 90); - Button add = SettingsUi.CreatePrimaryButton("+ Container hinzufügen"); - add.Location = new Point(8, 520); - add.Size = new Size(220, 40); - add.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; - add.Click += async (_, _) => await AddContainerAsync(); - - _panelContainers.Controls.Add(_lvContainers); - _panelContainers.Controls.Add(add); + FinishPanelLayout(_panelContainers, _lvContainers, toolbar); } private void BuildPathsPanel() @@ -387,13 +400,12 @@ public sealed class SetupWizardForm : Form "Zertifikat-Pfade", "Zielverzeichnisse und Dateinamen für Deployments."); - _lvPaths = SettingsUi.CreateListView(); - _lvPaths.Location = new Point(8, 80); - _lvPaths.Size = new Size(860, 420); - _lvPaths.Anchor = - AnchorStyles.Top | AnchorStyles.Bottom | - AnchorStyles.Left | AnchorStyles.Right; + Panel toolbar = CreateToolbar( + "+ Pfad hinzufügen", + async () => await AddPathAsync()); + _lvPaths = SettingsUi.CreateListView(); + _lvPaths.Dock = DockStyle.Fill; _lvPaths.Columns.Add("Company", 70); _lvPaths.Columns.Add("Container", 140); _lvPaths.Columns.Add("Verzeichnis", 280); @@ -401,14 +413,28 @@ public sealed class SetupWizardForm : Form _lvPaths.Columns.Add("Backup", 80); _lvPaths.Columns.Add("System", 140); - Button add = SettingsUi.CreatePrimaryButton("+ Pfad hinzufügen"); - add.Location = new Point(8, 520); - add.Size = new Size(200, 40); - add.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; - add.Click += async (_, _) => await AddPathAsync(); + FinishPanelLayout(_panelPaths, _lvPaths, toolbar); + } - _panelPaths.Controls.Add(_lvPaths); - _panelPaths.Controls.Add(add); + private Panel CreateToolbar( + string addButtonText, + Func onAdd) + { + Panel toolbar = new() + { + Dock = DockStyle.Top, + Height = 56, + BackColor = SettingsUi.Background, + Padding = new Padding(8, 8, 8, 8) + }; + + Button add = SettingsUi.CreatePrimaryButton(addButtonText); + add.Dock = DockStyle.Left; + add.Width = 230; + add.Click += async (_, _) => await onAdd(); + + toolbar.Controls.Add(add); + return toolbar; } private void BuildProfilesPanel() @@ -418,9 +444,8 @@ public sealed class SetupWizardForm : Form "Aktives Sonic-System und Benutzerprofil für Neustarts."); Panel card = SettingsUi.CreateCard(); - card.Location = new Point(8, 80); - card.Size = new Size(860, 280); - card.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + card.Dock = DockStyle.Fill; + card.Padding = new Padding(12); card.Paint += SettingsUi.PaintCardBorder; Label systemLabel = SettingsUi.CreateFieldLabel("Sonic-System"); @@ -465,7 +490,10 @@ public sealed class SetupWizardForm : Form card.Controls.Add(_btnAddCredential); card.Controls.Add(_lblSystemDetails); card.Controls.Add(_btnSaveSelection); + + Panel header = (Panel)_panelProfiles.Tag!; _panelProfiles.Controls.Add(card); + _panelProfiles.Controls.Add(header); } private static Label CreateStatusLabel(int x, int y) @@ -628,7 +656,8 @@ public sealed class SetupWizardForm : Form { using AddSonicSystemForm dialog = new(_coordinator); - if (dialog.ShowDialog(this) != DialogResult.OK) + if (dialog.ShowDialog(this) != DialogResult.OK + || dialog.CreatedSonicConnectionId is not int createdId) { return; } @@ -636,6 +665,33 @@ public sealed class SetupWizardForm : Form await LoadSystemsListAsync(); await LoadSystemsAsync(); await RefreshAllAsync(); + + bool visible = _lvSystems.Items + .Cast() + .Any(item => + item.Tag is SonicSystemOption system + && system.SonicConnectionId == createdId); + + if (!visible) + { + ShowError( + $"Anlage-Prüfung fehlgeschlagen: System-Id {createdId} " + + "ist nach dem Speichern nicht in der Liste."); + return; + } + + SelectListItem( + _lvSystems, + item => + item.Tag is SonicSystemOption system + && system.SonicConnectionId == createdId); + + MessageBox.Show( + this, + $"System wurde gespeichert und geprüft (Id {createdId}).", + "Prüfung OK", + MessageBoxButtons.OK, + MessageBoxIcon.Information); } private async Task AddContainerAsync() @@ -650,7 +706,8 @@ public sealed class SetupWizardForm : Form using AddContainerForm dialog = new(_coordinator, preferred); - if (dialog.ShowDialog(this) != DialogResult.OK) + if (dialog.ShowDialog(this) != DialogResult.OK + || dialog.CreatedSonicContainerId is not int createdId) { return; } @@ -658,6 +715,33 @@ public sealed class SetupWizardForm : Form await LoadContainersListAsync(); await LoadSystemsListAsync(); await RefreshAllAsync(); + + bool visible = _lvContainers.Items + .Cast() + .Any(item => + item.Tag is ContainerOption container + && container.SonicContainerId == createdId); + + if (!visible) + { + ShowError( + $"Anlage-Prüfung fehlgeschlagen: Container-Id {createdId} " + + "ist nach dem Speichern nicht in der Liste."); + return; + } + + SelectListItem( + _lvContainers, + item => + item.Tag is ContainerOption container + && container.SonicContainerId == createdId); + + MessageBox.Show( + this, + $"Container wurde gespeichert und geprüft (Id {createdId}).", + "Prüfung OK", + MessageBoxButtons.OK, + MessageBoxIcon.Information); } private async Task AddPathAsync() @@ -672,13 +756,59 @@ public sealed class SetupWizardForm : Form using AddTargetPathForm dialog = new(_coordinator, preferred); - if (dialog.ShowDialog(this) != DialogResult.OK) + if (dialog.ShowDialog(this) != DialogResult.OK + || dialog.CreatedCertificateTargetId is not int createdId) { return; } await LoadPathsListAsync(); await RefreshAllAsync(); + + bool visible = _lvPaths.Items + .Cast() + .Any(item => + item.Tag is CertificateTargetOption target + && target.CertificateTargetId == createdId); + + if (!visible) + { + ShowError( + $"Anlage-Prüfung fehlgeschlagen: Pfad-Id {createdId} " + + "ist nach dem Speichern nicht in der Liste."); + return; + } + + SelectListItem( + _lvPaths, + item => + item.Tag is CertificateTargetOption target + && target.CertificateTargetId == createdId); + + MessageBox.Show( + this, + $"Zertifikat-Pfad wurde gespeichert und geprüft (Id {createdId}).", + "Prüfung OK", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + + private static void SelectListItem( + ListView listView, + Func match) + { + foreach (ListViewItem item in listView.Items) + { + if (!match(item)) + { + continue; + } + + item.Selected = true; + item.EnsureVisible(); + listView.Focus(); + return; + } } private async Task LoadSystemsAsync()