Fix settings add buttons and verify catalog inserts against SQL.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-29 08:58:16 +02:00
co-authored by Cursor
parent ed6777ffed
commit 09b31a4ac9
3 changed files with 340 additions and 63 deletions
@@ -1299,6 +1299,81 @@ public sealed class SonicSetupRepository
return Convert.ToInt32(result); return Convert.ToInt32(result);
} }
public async Task<bool> 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<bool> 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<bool> 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<bool> 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( private static string RequireTrimmed(
string? value, string? value,
int maxLength, int maxLength,
@@ -352,7 +352,7 @@ public sealed class SetupCoordinator
cancellationToken); cancellationToken);
} }
public Task<int> AddSonicConnectionAsync( public async Task<int> AddSonicConnectionAsync(
string connectionName, string connectionName,
string managementHost, string managementHost,
int managementPort, int managementPort,
@@ -360,7 +360,7 @@ public sealed class SetupCoordinator
string connectionProtocol, string connectionProtocol,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return _setupRepository.AddSonicConnectionAsync( int id = await _setupRepository.AddSonicConnectionAsync(
_settings.EnvironmentCode, _settings.EnvironmentCode,
connectionName, connectionName,
managementHost, managementHost,
@@ -368,9 +368,29 @@ public sealed class SetupCoordinator
domainName, domainName,
connectionProtocol, connectionProtocol,
cancellationToken); cancellationToken);
await EnsureExistsAsync(
() => _setupRepository.ExistsSonicConnectionAsync(
id,
cancellationToken),
$"Sonic-System wurde mit Id {id} geschrieben, " +
"ist danach in SQL aber nicht auffindbar.");
IReadOnlyList<SonicSystemOption> 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.");
} }
public Task<int> AddSonicContainerAsync( return id;
}
public async Task<int> AddSonicContainerAsync(
int companyId, int companyId,
int sonicConnectionId, int sonicConnectionId,
string containerName, string containerName,
@@ -378,16 +398,37 @@ public sealed class SetupCoordinator
int restartTimeoutSeconds, int restartTimeoutSeconds,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return _setupRepository.AddSonicContainerAsync( int id = await _setupRepository.AddSonicContainerAsync(
companyId, companyId,
sonicConnectionId, sonicConnectionId,
containerName, containerName,
containerDisplayName, containerDisplayName,
restartTimeoutSeconds, restartTimeoutSeconds,
cancellationToken); cancellationToken);
await EnsureExistsAsync(
() => _setupRepository.ExistsSonicContainerAsync(
id,
cancellationToken),
$"Container wurde mit Id {id} geschrieben, " +
"ist danach in SQL aber nicht auffindbar.");
IReadOnlyList<ContainerOption> 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.");
} }
public Task<int> AddCertificateTargetAsync( return id;
}
public async Task<int> AddCertificateTargetAsync(
int sonicContainerId, int sonicContainerId,
string targetDirectory, string targetDirectory,
string targetFileName, string targetFileName,
@@ -395,13 +436,44 @@ public sealed class SetupCoordinator
string backupDirectoryName, string backupDirectoryName,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return _setupRepository.AddCertificateTargetAsync( int id = await _setupRepository.AddCertificateTargetAsync(
sonicContainerId, sonicContainerId,
targetDirectory, targetDirectory,
targetFileName, targetFileName,
backupEnabled, backupEnabled,
backupDirectoryName, backupDirectoryName,
cancellationToken); cancellationToken);
await EnsureExistsAsync(
() => _setupRepository.ExistsCertificateTargetAsync(
id,
cancellationToken),
$"Zertifikat-Pfad wurde mit Id {id} geschrieben, " +
"ist danach in SQL aber nicht auffindbar.");
IReadOnlyList<CertificateTargetOption> 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<Task<bool>> existsCheck,
string errorMessage)
{
if (!await existsCheck())
{
throw new InvalidOperationException(errorMessage);
}
} }
public void SaveSelection( public void SaveSelection(
@@ -252,14 +252,32 @@ public sealed class SetupWizardForm : Form
{ {
Dock = DockStyle.Fill, Dock = DockStyle.Fill,
BackColor = SettingsUi.Background, 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() Label heading = new()
{ {
Text = title, Text = title,
AutoSize = true, AutoSize = true,
Location = new Point(8, 4), Location = new Point(4, 2),
ForeColor = SettingsUi.Text, ForeColor = SettingsUi.Text,
Font = new Font("Segoe UI Semibold", 22f, FontStyle.Bold) Font = new Font("Segoe UI Semibold", 22f, FontStyle.Bold)
}; };
@@ -268,15 +286,24 @@ public sealed class SetupWizardForm : Form
{ {
Text = subtitle, Text = subtitle,
AutoSize = true, AutoSize = true,
Location = new Point(12, 42), Location = new Point(8, 40),
ForeColor = SettingsUi.Muted, ForeColor = SettingsUi.Muted,
Font = new Font("Segoe UI", 10f) Font = new Font("Segoe UI", 10f)
}; };
panel.Controls.Add(heading); header.Controls.Add(heading);
panel.Controls.Add(sub); header.Controls.Add(sub);
_contentHost.Controls.Add(panel); return header;
return panel; }
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() private void BuildStatusPanel()
@@ -286,9 +313,8 @@ public sealed class SetupWizardForm : Form
"Runtime, Zertifikat, Datenbank und aktuelle Auswahl."); "Runtime, Zertifikat, Datenbank und aktuelle Auswahl.");
Panel card = SettingsUi.CreateCard(); Panel card = SettingsUi.CreateCard();
card.Location = new Point(8, 80); card.Dock = DockStyle.Fill;
card.Size = new Size(860, 280); card.Padding = new Padding(12);
card.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
card.Paint += SettingsUi.PaintCardBorder; card.Paint += SettingsUi.PaintCardBorder;
_lblRuntime = CreateStatusLabel(20, 24); _lblRuntime = CreateStatusLabel(20, 24);
@@ -319,7 +345,10 @@ public sealed class SetupWizardForm : Form
card.Controls.Add(_lblSelection); card.Controls.Add(_lblSelection);
card.Controls.Add(_btnImportCertificate); card.Controls.Add(_btnImportCertificate);
card.Controls.Add(tip); card.Controls.Add(tip);
Panel header = (Panel)_panelStatus.Tag!;
_panelStatus.Controls.Add(card); _panelStatus.Controls.Add(card);
_panelStatus.Controls.Add(header);
} }
private void BuildSystemsPanel() private void BuildSystemsPanel()
@@ -328,13 +357,12 @@ public sealed class SetupWizardForm : Form
"Sonic-Systeme", "Sonic-Systeme",
"Management-Verbindungen der aktuellen Umgebung."); "Management-Verbindungen der aktuellen Umgebung.");
_lvSystems = SettingsUi.CreateListView(); Panel toolbar = CreateToolbar(
_lvSystems.Location = new Point(8, 80); "+ System hinzufügen",
_lvSystems.Size = new Size(860, 420); async () => await AddSystemAsync());
_lvSystems.Anchor =
AnchorStyles.Top | AnchorStyles.Bottom |
AnchorStyles.Left | AnchorStyles.Right;
_lvSystems = SettingsUi.CreateListView();
_lvSystems.Dock = DockStyle.Fill;
_lvSystems.Columns.Add("Name", 160); _lvSystems.Columns.Add("Name", 160);
_lvSystems.Columns.Add("Host", 180); _lvSystems.Columns.Add("Host", 180);
_lvSystems.Columns.Add("Port", 70); _lvSystems.Columns.Add("Port", 70);
@@ -342,14 +370,7 @@ public sealed class SetupWizardForm : Form
_lvSystems.Columns.Add("Protokoll", 80); _lvSystems.Columns.Add("Protokoll", 80);
_lvSystems.Columns.Add("URL", 180); _lvSystems.Columns.Add("URL", 180);
Button add = SettingsUi.CreatePrimaryButton("+ System hinzufügen"); FinishPanelLayout(_panelSystems, _lvSystems, toolbar);
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);
} }
private void BuildContainersPanel() private void BuildContainersPanel()
@@ -358,27 +379,19 @@ public sealed class SetupWizardForm : Form
"Container", "Container",
"Sonic-Container je Company und System."); "Sonic-Container je Company und System.");
_lvContainers = SettingsUi.CreateListView(); Panel toolbar = CreateToolbar(
_lvContainers.Location = new Point(8, 80); "+ Container hinzufügen",
_lvContainers.Size = new Size(860, 420); async () => await AddContainerAsync());
_lvContainers.Anchor =
AnchorStyles.Top | AnchorStyles.Bottom |
AnchorStyles.Left | AnchorStyles.Right;
_lvContainers = SettingsUi.CreateListView();
_lvContainers.Dock = DockStyle.Fill;
_lvContainers.Columns.Add("Company", 80); _lvContainers.Columns.Add("Company", 80);
_lvContainers.Columns.Add("Container", 180); _lvContainers.Columns.Add("Container", 180);
_lvContainers.Columns.Add("Anzeige", 160); _lvContainers.Columns.Add("Anzeige", 160);
_lvContainers.Columns.Add("System", 160); _lvContainers.Columns.Add("System", 160);
_lvContainers.Columns.Add("Timeout", 90); _lvContainers.Columns.Add("Timeout", 90);
Button add = SettingsUi.CreatePrimaryButton("+ Container hinzufügen"); FinishPanelLayout(_panelContainers, _lvContainers, toolbar);
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);
} }
private void BuildPathsPanel() private void BuildPathsPanel()
@@ -387,13 +400,12 @@ public sealed class SetupWizardForm : Form
"Zertifikat-Pfade", "Zertifikat-Pfade",
"Zielverzeichnisse und Dateinamen für Deployments."); "Zielverzeichnisse und Dateinamen für Deployments.");
_lvPaths = SettingsUi.CreateListView(); Panel toolbar = CreateToolbar(
_lvPaths.Location = new Point(8, 80); "+ Pfad hinzufügen",
_lvPaths.Size = new Size(860, 420); async () => await AddPathAsync());
_lvPaths.Anchor =
AnchorStyles.Top | AnchorStyles.Bottom |
AnchorStyles.Left | AnchorStyles.Right;
_lvPaths = SettingsUi.CreateListView();
_lvPaths.Dock = DockStyle.Fill;
_lvPaths.Columns.Add("Company", 70); _lvPaths.Columns.Add("Company", 70);
_lvPaths.Columns.Add("Container", 140); _lvPaths.Columns.Add("Container", 140);
_lvPaths.Columns.Add("Verzeichnis", 280); _lvPaths.Columns.Add("Verzeichnis", 280);
@@ -401,14 +413,28 @@ public sealed class SetupWizardForm : Form
_lvPaths.Columns.Add("Backup", 80); _lvPaths.Columns.Add("Backup", 80);
_lvPaths.Columns.Add("System", 140); _lvPaths.Columns.Add("System", 140);
Button add = SettingsUi.CreatePrimaryButton("+ Pfad hinzufügen"); FinishPanelLayout(_panelPaths, _lvPaths, toolbar);
add.Location = new Point(8, 520); }
add.Size = new Size(200, 40);
add.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
add.Click += async (_, _) => await AddPathAsync();
_panelPaths.Controls.Add(_lvPaths); private Panel CreateToolbar(
_panelPaths.Controls.Add(add); string addButtonText,
Func<Task> 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() private void BuildProfilesPanel()
@@ -418,9 +444,8 @@ public sealed class SetupWizardForm : Form
"Aktives Sonic-System und Benutzerprofil für Neustarts."); "Aktives Sonic-System und Benutzerprofil für Neustarts.");
Panel card = SettingsUi.CreateCard(); Panel card = SettingsUi.CreateCard();
card.Location = new Point(8, 80); card.Dock = DockStyle.Fill;
card.Size = new Size(860, 280); card.Padding = new Padding(12);
card.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
card.Paint += SettingsUi.PaintCardBorder; card.Paint += SettingsUi.PaintCardBorder;
Label systemLabel = SettingsUi.CreateFieldLabel("Sonic-System"); Label systemLabel = SettingsUi.CreateFieldLabel("Sonic-System");
@@ -465,7 +490,10 @@ public sealed class SetupWizardForm : Form
card.Controls.Add(_btnAddCredential); card.Controls.Add(_btnAddCredential);
card.Controls.Add(_lblSystemDetails); card.Controls.Add(_lblSystemDetails);
card.Controls.Add(_btnSaveSelection); card.Controls.Add(_btnSaveSelection);
Panel header = (Panel)_panelProfiles.Tag!;
_panelProfiles.Controls.Add(card); _panelProfiles.Controls.Add(card);
_panelProfiles.Controls.Add(header);
} }
private static Label CreateStatusLabel(int x, int y) private static Label CreateStatusLabel(int x, int y)
@@ -628,7 +656,8 @@ public sealed class SetupWizardForm : Form
{ {
using AddSonicSystemForm dialog = new(_coordinator); using AddSonicSystemForm dialog = new(_coordinator);
if (dialog.ShowDialog(this) != DialogResult.OK) if (dialog.ShowDialog(this) != DialogResult.OK
|| dialog.CreatedSonicConnectionId is not int createdId)
{ {
return; return;
} }
@@ -636,6 +665,33 @@ public sealed class SetupWizardForm : Form
await LoadSystemsListAsync(); await LoadSystemsListAsync();
await LoadSystemsAsync(); await LoadSystemsAsync();
await RefreshAllAsync(); await RefreshAllAsync();
bool visible = _lvSystems.Items
.Cast<ListViewItem>()
.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() private async Task AddContainerAsync()
@@ -650,7 +706,8 @@ public sealed class SetupWizardForm : Form
using AddContainerForm dialog = new(_coordinator, preferred); 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; return;
} }
@@ -658,6 +715,33 @@ public sealed class SetupWizardForm : Form
await LoadContainersListAsync(); await LoadContainersListAsync();
await LoadSystemsListAsync(); await LoadSystemsListAsync();
await RefreshAllAsync(); await RefreshAllAsync();
bool visible = _lvContainers.Items
.Cast<ListViewItem>()
.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() private async Task AddPathAsync()
@@ -672,13 +756,59 @@ public sealed class SetupWizardForm : Form
using AddTargetPathForm dialog = new(_coordinator, preferred); 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; return;
} }
await LoadPathsListAsync(); await LoadPathsListAsync();
await RefreshAllAsync(); await RefreshAllAsync();
bool visible = _lvPaths.Items
.Cast<ListViewItem>()
.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<ListViewItem, bool> match)
{
foreach (ListViewItem item in listView.Items)
{
if (!match(item))
{
continue;
}
item.Selected = true;
item.EnsureVisible();
listView.Focus();
return;
}
} }
private async Task LoadSystemsAsync() private async Task LoadSystemsAsync()