Files
123123/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs
GizzlerandCursor 58b7826162 Add live TLS certificate probing and improve restart error handling.
Configure CertificateCheckUrl per container for curl-like TLS checks, classify Sonic permission errors, and extend setup wizard for container management.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 13:16:21 +02:00

1524 lines
44 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using ZA.CoreService.ESBCertificateManager.Models;
using ZA.CoreService.ESBCertificateManager.Services;
namespace ZA.CoreService.ESBCertificateManager.Setup;
public sealed class SetupWizardForm : Form
{
private readonly SetupCoordinator _coordinator;
private readonly bool _isSettingsMode;
private Panel _sidebar = null!;
private Panel _contentHost = null!;
private Panel _panelStatus = null!;
private Panel _panelSystems = null!;
private Panel _panelContainers = null!;
private Panel _panelPaths = null!;
private Panel _panelProfiles = null!;
private readonly List<Button> _navButtons = [];
private Button _activeNav = null!;
private Label _lblRuntime = null!;
private Label _lblCertificate = null!;
private Label _lblDatabase = null!;
private Label _lblSelection = null!;
private ListView _lvSystems = null!;
private ListView _lvContainers = null!;
private ListView _lvPaths = null!;
private ComboBox _cmbSystem = null!;
private ComboBox _cmbCredential = null!;
private Label _lblSystemDetails = null!;
private Button _btnImportCertificate = null!;
private Button _btnSaveSelection = null!;
private Button _btnFinish = null!;
private Button _btnAddCredential = null!;
private Button _btnEditCredential = null!;
private IReadOnlyList<SonicSystemOption> _systems = [];
private IReadOnlyList<SonicCredentialProfile> _profiles = [];
private bool _running;
public SetupWizardForm(
SetupCoordinator coordinator,
bool isSettingsMode = false)
{
_coordinator = coordinator;
_isSettingsMode = isSettingsMode;
Text = "Einstellungen";
StartPosition = FormStartPosition.CenterScreen;
Size = new Size(1180, 760);
MinimumSize = new Size(1080, 700);
BackColor = SettingsUi.Background;
ForeColor = SettingsUi.Text;
Font = new Font("Segoe UI", 10f);
DoubleBuffered = true;
BuildShell();
BuildStatusPanel();
BuildSystemsPanel();
BuildContainersPanel();
BuildPathsPanel();
BuildProfilesPanel();
ShowPanel(_panelStatus, _navButtons[0]);
Shown += async (_, _) => await RefreshAllAsync();
FormClosing += (_, e) =>
{
// X-Button: in Einstellungen trotzdem als „geschlossen mit Änderungen möglich“
if (_isSettingsMode && DialogResult == DialogResult.None)
{
DialogResult = DialogResult.OK;
}
};
}
private void BuildShell()
{
_sidebar = new Panel
{
Dock = DockStyle.Left,
Width = 230,
BackColor = SettingsUi.Sidebar
};
Label brand = new()
{
Text = "ESB Certificate",
Location = new Point(22, 28),
AutoSize = true,
ForeColor = SettingsUi.Gold,
Font = new Font("Segoe UI Semibold", 11f, FontStyle.Bold)
};
Label brandSub = new()
{
Text = "Einstellungen",
Location = new Point(22, 52),
AutoSize = true,
ForeColor = SettingsUi.Text,
Font = new Font("Segoe UI Semibold", 20f, FontStyle.Bold)
};
_sidebar.Controls.Add(brand);
_sidebar.Controls.Add(brandSub);
string[] navItems =
[
"Prüfung",
"Systeme",
"Container",
"Pfade",
"Profile"
];
int y = 120;
foreach (string item in navItems)
{
Button nav = CreateNavButton(item);
nav.Location = new Point(14, y);
y += 48;
_navButtons.Add(nav);
_sidebar.Controls.Add(nav);
}
_navButtons[0].Click += (_, _) =>
ShowPanel(_panelStatus, _navButtons[0]);
_navButtons[1].Click += async (_, _) =>
{
ShowPanel(_panelSystems, _navButtons[1]);
await LoadSystemsListAsync();
};
_navButtons[2].Click += async (_, _) =>
{
ShowPanel(_panelContainers, _navButtons[2]);
await LoadContainersListAsync();
};
_navButtons[3].Click += async (_, _) =>
{
ShowPanel(_panelPaths, _navButtons[3]);
await LoadPathsListAsync();
};
_navButtons[4].Click += async (_, _) =>
{
ShowPanel(_panelProfiles, _navButtons[4]);
await LoadSystemsAsync();
};
Panel footer = new()
{
Dock = DockStyle.Bottom,
Height = 72,
BackColor = SettingsUi.Background
};
Button closeButton = SettingsUi.CreateGhostButton(
_isSettingsMode ? "Schließen" : "Abbrechen");
closeButton.Location = new Point(Width - 340, 16);
closeButton.Size = new Size(120, 40);
closeButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
closeButton.Click += async (_, _) => await CloseAsync();
_btnFinish = SettingsUi.CreatePrimaryButton(
_isSettingsMode ? "Übernehmen" : "Fertig");
_btnFinish.Location = new Point(Width - 200, 16);
_btnFinish.Size = new Size(160, 40);
_btnFinish.Anchor = AnchorStyles.Top | AnchorStyles.Right;
_btnFinish.Enabled = false;
_btnFinish.Click += (_, _) =>
{
DialogResult = DialogResult.OK;
Close();
};
Button refresh = SettingsUi.CreateGhostButton("Aktualisieren");
refresh.Location = new Point(250, 16);
refresh.Size = new Size(130, 40);
refresh.Click += async (_, _) => await RefreshAllAsync();
footer.Controls.Add(refresh);
footer.Controls.Add(closeButton);
footer.Controls.Add(_btnFinish);
_contentHost = new Panel
{
Dock = DockStyle.Fill,
BackColor = SettingsUi.Background,
Padding = new Padding(28, 24, 28, 12)
};
Controls.Add(_contentHost);
Controls.Add(footer);
Controls.Add(_sidebar);
Resize += (_, _) =>
{
closeButton.Left = footer.Width - 340;
_btnFinish.Left = footer.Width - 200;
};
}
private Button CreateNavButton(string text)
{
Button button = new()
{
Text = " " + text,
Size = new Size(200, 42),
FlatStyle = FlatStyle.Flat,
TextAlign = ContentAlignment.MiddleLeft,
BackColor = SettingsUi.Sidebar,
ForeColor = SettingsUi.Muted,
Font = new Font("Segoe UI Semibold", 10.5f, FontStyle.Bold),
Cursor = Cursors.Hand
};
button.FlatAppearance.BorderSize = 0;
button.FlatAppearance.MouseOverBackColor = SettingsUi.CardHover;
return button;
}
private void ShowPanel(Panel panel, Button nav)
{
foreach (Control control in _contentHost.Controls)
{
control.Visible = false;
}
panel.Visible = true;
panel.BringToFront();
foreach (Button button in _navButtons)
{
button.BackColor = SettingsUi.Sidebar;
button.ForeColor = SettingsUi.Muted;
}
nav.BackColor = SettingsUi.Card;
nav.ForeColor = SettingsUi.Gold;
_activeNav = nav;
}
private Panel CreateContentPanel(string title, string subtitle)
{
Panel panel = new()
{
Dock = DockStyle.Fill,
BackColor = SettingsUi.Background,
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(4, 2),
ForeColor = SettingsUi.Text,
Font = new Font("Segoe UI Semibold", 22f, FontStyle.Bold)
};
Label sub = new()
{
Text = subtitle,
AutoSize = true,
Location = new Point(8, 40),
ForeColor = SettingsUi.Muted,
Font = new Font("Segoe UI", 10f)
};
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()
{
_panelStatus = CreateContentPanel(
"Systemprüfung",
"Runtime, Zertifikat, Datenbank und aktuelle Auswahl.");
Panel card = SettingsUi.CreateCard();
card.Dock = DockStyle.Fill;
card.Padding = new Padding(12);
card.Paint += SettingsUi.PaintCardBorder;
_lblRuntime = CreateStatusLabel(20, 24);
_lblCertificate = CreateStatusLabel(20, 78);
_lblDatabase = CreateStatusLabel(20, 132);
_lblSelection = CreateStatusLabel(20, 186);
_btnImportCertificate = SettingsUi.CreatePrimaryButton("PFX importieren");
_btnImportCertificate.Location = new Point(640, 72);
_btnImportCertificate.Size = new Size(180, 38);
_btnImportCertificate.Anchor = AnchorStyles.Top | AnchorStyles.Right;
_btnImportCertificate.Click += async (_, _) =>
await ImportCertificateAsync();
Label tip = new()
{
Text =
"Tipp: Unter „Systeme“, „Container“ und „Pfade“ kannst du " +
"die Katalogdaten für die aktuelle Umgebung pflegen.",
Location = new Point(20, 235),
Size = new Size(780, 28),
ForeColor = SettingsUi.Muted
};
card.Controls.Add(_lblRuntime);
card.Controls.Add(_lblCertificate);
card.Controls.Add(_lblDatabase);
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()
{
_panelSystems = CreateContentPanel(
"Sonic-Systeme",
"Management-Verbindungen der aktuellen Umgebung.");
Panel toolbar = CreateToolbar(
("+ 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;
_lvSystems.Columns.Add("Name", 160);
_lvSystems.Columns.Add("Host", 180);
_lvSystems.Columns.Add("Port", 70);
_lvSystems.Columns.Add("Domain", 180);
_lvSystems.Columns.Add("Protokoll", 80);
_lvSystems.Columns.Add("URL", 180);
_lvSystems.DoubleClick += async (_, _) => await EditSelectedSystemAsync();
FinishPanelLayout(_panelSystems, _lvSystems, toolbar);
}
private void BuildContainersPanel()
{
_panelContainers = CreateContentPanel(
"Container",
"Sonic-Container je Company und System.");
Panel toolbar = CreateToolbar(
("+ 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;
_lvContainers.Columns.Add("Company", 70);
_lvContainers.Columns.Add("Container", 150);
_lvContainers.Columns.Add("Anzeige", 120);
_lvContainers.Columns.Add("System", 120);
_lvContainers.Columns.Add("Prüf-URL", 180);
_lvContainers.Columns.Add("Timeout", 80);
_lvContainers.DoubleClick += async (_, _) => await EditSelectedContainerAsync();
FinishPanelLayout(_panelContainers, _lvContainers, toolbar);
}
private void BuildPathsPanel()
{
_panelPaths = CreateContentPanel(
"Zertifikat-Pfade",
"Dateipfade (UNC/lokal) zum Austausch. Container = nur Neustart-Ziel.");
Panel toolbar = CreateToolbar(
("+ 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;
_lvPaths.Columns.Add("Company", 70);
_lvPaths.Columns.Add("Neustart-Container", 140);
_lvPaths.Columns.Add("Zertifikat-Pfad", 360);
_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 static Panel CreateToolbar(
params (string Text, Func<Task> Action, bool Primary, int Width)[] buttons)
{
Panel toolbar = new()
{
Dock = DockStyle.Top,
Height = 56,
BackColor = SettingsUi.Background,
Padding = new Padding(8, 8, 8, 8)
};
// Dock Left: zuletzt hinzugefügt erscheint ganz links
for (int index = buttons.Length - 1; index >= 0; index--)
{
(string text, Func<Task> 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);
}
return toolbar;
}
private void BuildProfilesPanel()
{
_panelProfiles = CreateContentPanel(
"Profil & Auswahl",
"Aktives Sonic-System und Benutzerprofil für Neustarts.");
Panel card = SettingsUi.CreateCard();
card.Dock = DockStyle.Fill;
card.Padding = new Padding(12);
card.Paint += SettingsUi.PaintCardBorder;
Label systemLabel = SettingsUi.CreateFieldLabel("Sonic-System");
systemLabel.Location = new Point(24, 24);
_cmbSystem = SettingsUi.CreateComboBox();
_cmbSystem.Location = new Point(24, 48);
_cmbSystem.Width = 340;
_cmbSystem.SelectedIndexChanged += async (_, _) =>
await SystemChangedAsync();
Label credentialLabel = SettingsUi.CreateFieldLabel("Benutzerprofil");
credentialLabel.Location = new Point(24, 100);
_cmbCredential = SettingsUi.CreateComboBox();
_cmbCredential.Location = new Point(24, 124);
_cmbCredential.Width = 340;
_btnAddCredential = SettingsUi.CreateGhostButton("Profil hinzufügen");
_btnAddCredential.Location = new Point(380, 122);
_btnAddCredential.Size = new Size(160, 34);
_btnAddCredential.Click += async (_, _) =>
await AddCredentialAsync();
_btnEditCredential = SettingsUi.CreateGhostButton("Profil bearbeiten");
_btnEditCredential.Location = new Point(550, 122);
_btnEditCredential.Size = new Size(160, 34);
_btnEditCredential.Click += async (_, _) =>
await EditCredentialAsync();
_lblSystemDetails = new Label
{
Location = new Point(24, 180),
Size = new Size(780, 50),
ForeColor = SettingsUi.Muted
};
_btnSaveSelection = SettingsUi.CreatePrimaryButton("Auswahl übernehmen");
_btnSaveSelection.Location = new Point(600, 220);
_btnSaveSelection.Size = new Size(220, 40);
_btnSaveSelection.Click += async (_, _) =>
await SaveSelectionAsync();
card.Controls.Add(systemLabel);
card.Controls.Add(_cmbSystem);
card.Controls.Add(credentialLabel);
card.Controls.Add(_cmbCredential);
card.Controls.Add(_btnAddCredential);
card.Controls.Add(_btnEditCredential);
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)
{
return new Label
{
Location = new Point(x, y),
Size = new Size(600, 40),
ForeColor = SettingsUi.Muted,
Font = new Font("Segoe UI", 10.5f)
};
}
private async Task RefreshAllAsync()
{
if (_running)
{
return;
}
SetRunning(true);
try
{
SetupCheckResult result =
await _coordinator.CheckAsync();
SetStatus(
_lblRuntime,
"Runtime",
result.RuntimeReady,
result.RuntimeMessage);
SetStatus(
_lblCertificate,
"Always Encrypted",
result.CertificateReady,
result.CertificateMessage);
SetStatus(
_lblDatabase,
"Datenbank",
result.DatabaseReady,
result.DatabaseMessage);
SetStatus(
_lblSelection,
"Auswahl",
result.CredentialsReady,
result.CredentialsMessage);
_btnImportCertificate.Visible = !result.CertificateReady;
_btnFinish.Enabled = result.IsReady;
if (result.CertificateReady)
{
await LoadSystemsAsync();
await LoadSystemsListAsync();
await LoadContainersListAsync();
await LoadPathsListAsync();
}
}
catch (Exception ex)
{
MessageBox.Show(
this,
ex.Message,
"Einstellungen",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
finally
{
SetRunning(false);
}
}
private async Task LoadSystemsListAsync()
{
try
{
_systems = await _coordinator.LoadSystemsAsync();
_lvSystems.Items.Clear();
foreach (SonicSystemOption system in _systems)
{
ListViewItem item = new(system.ConnectionName);
item.SubItems.Add(system.ManagementHost);
item.SubItems.Add(system.ManagementPort.ToString());
item.SubItems.Add(system.DomainName);
item.SubItems.Add(system.ConnectionProtocol);
item.SubItems.Add(system.ConnectionUrl);
item.Tag = system;
_lvSystems.Items.Add(item);
}
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
private async Task LoadContainersListAsync()
{
try
{
IReadOnlyList<ContainerOption> containers =
await _coordinator.LoadContainersAsync();
_lvContainers.Items.Clear();
foreach (ContainerOption container in containers)
{
ListViewItem item = new(container.CompanyCode);
item.SubItems.Add(container.ContainerName);
item.SubItems.Add(container.ContainerDisplayName ?? "—");
item.SubItems.Add(container.ConnectionName);
item.SubItems.Add(
string.IsNullOrWhiteSpace(container.CertificateCheckUrl)
? "—"
: container.CertificateCheckUrl);
item.SubItems.Add(container.RestartTimeoutSeconds + " s");
item.Tag = container;
_lvContainers.Items.Add(item);
}
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
private async Task LoadPathsListAsync()
{
try
{
IReadOnlyList<CertificateTargetOption> targets =
await _coordinator.LoadCertificateTargetsAsync();
_lvPaths.Items.Clear();
CertificateProbeService probe = new();
foreach (CertificateTargetOption target in targets)
{
ListViewItem item = new(target.CompanyCode);
item.SubItems.Add(target.ContainerName);
item.SubItems.Add(target.FullTargetPath);
item.SubItems.Add("…");
item.SubItems.Add(
target.BackupEnabled
? target.BackupDirectoryName
: "aus");
item.SubItems.Add(target.ConnectionName);
item.Tag = target;
_lvPaths.Items.Add(item);
CertificateProbeResult result = await Task.Run(
() => probe.Probe(target.FullTargetPath));
item.SubItems[3].Text = FormatProbeExpiry(result);
}
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
private async Task ProbeSelectedPathAsync()
{
if (_lvPaths.SelectedItems.Count == 0
|| _lvPaths.SelectedItems[0].Tag is not CertificateTargetOption target)
{
MessageBox.Show(
this,
"Bitte auswählen.",
"Pfad prüfen",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
CertificateProbeService probe = new();
CertificateProbeResult result = probe.Probe(
target.FullTargetPath,
() => PromptCertificatePassword(this));
_lvPaths.SelectedItems[0].SubItems[3].Text =
FormatProbeExpiry(result);
MessageBoxIcon icon = result.Success && result.Certificate is not null
? (result.Certificate.IsCurrentlyValid
? MessageBoxIcon.Information
: MessageBoxIcon.Warning)
: MessageBoxIcon.Warning;
MessageBox.Show(
this,
result.Success ? "Pfad OK." : "Prüfung fehlgeschlagen.",
"Pfad prüfen",
MessageBoxButtons.OK,
icon);
}
private static string FormatProbeExpiry(CertificateProbeResult result)
{
if (result.Success && result.Certificate is not null)
{
return result.Certificate.ValidUntil
.ToLocalTime()
.ToString("dd.MM.yyyy HH:mm");
}
if (result.FileExists)
{
return "Kennwort nötig";
}
if (result.AccessDenied)
{
return CertificateProbeResult.AccessDeniedShortText;
}
return result.Success ? "fehlt" : "nicht erreichbar";
}
private static string? PromptCertificatePassword(IWin32Window owner)
{
using Form dialog = new()
{
Text = "PFX/P12-Kennwort",
FormBorderStyle = FormBorderStyle.FixedDialog,
StartPosition = FormStartPosition.CenterParent,
ClientSize = new Size(360, 140),
MaximizeBox = false,
MinimizeBox = false,
ShowInTaskbar = false,
BackColor = SettingsUi.Background,
ForeColor = SettingsUi.Text
};
Label label = new()
{
Text = "Kennwort für die Zertifikatsdatei:",
Location = new Point(16, 16),
AutoSize = true
};
TextBox txtPwd = new()
{
Location = new Point(16, 44),
Width = 320,
UseSystemPasswordChar = true
};
Button btnOk = SettingsUi.CreatePrimaryButton("OK");
btnOk.Location = new Point(196, 90);
btnOk.Size = new Size(140, 32);
btnOk.DialogResult = DialogResult.OK;
Button btnCancel = SettingsUi.CreateGhostButton("Abbrechen");
btnCancel.Location = new Point(100, 90);
btnCancel.Size = new Size(90, 32);
btnCancel.DialogResult = DialogResult.Cancel;
dialog.Controls.Add(label);
dialog.Controls.Add(txtPwd);
dialog.Controls.Add(btnOk);
dialog.Controls.Add(btnCancel);
dialog.AcceptButton = btnOk;
dialog.CancelButton = btnCancel;
return dialog.ShowDialog(owner) == DialogResult.OK
? txtPwd.Text
: null;
}
private async Task AddSystemAsync()
{
using AddSonicSystemForm dialog = new(_coordinator);
if (dialog.ShowDialog(this) != DialogResult.OK
|| dialog.CreatedSonicConnectionId is not int createdId)
{
return;
}
await AfterSystemSavedAsync(createdId);
}
private async Task EditSelectedSystemAsync()
{
if (_lvSystems.SelectedItems.Count == 0
|| _lvSystems.SelectedItems[0].Tag is not SonicSystemOption system)
{
MessageBox.Show(
this,
"Bitte auswä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);
}
private async Task DeleteSelectedSystemAsync()
{
if (_lvSystems.SelectedItems.Count == 0
|| _lvSystems.SelectedItems[0].Tag is not SonicSystemOption system)
{
MessageBox.Show(
this,
"Bitte auswählen.",
"System löschen",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
DialogResult confirm = MessageBox.Show(
this,
"System entfernen?",
"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,
"Entfernt.",
"System gelöscht",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
private async Task AfterSystemSavedAsync(int systemId)
{
await LoadSystemsListAsync();
await LoadSystemsAsync();
await RefreshAllAsync();
bool visible = _lvSystems.Items
.Cast<ListViewItem>()
.Any(item =>
item.Tag is SonicSystemOption system
&& system.SonicConnectionId == systemId);
if (!visible)
{
ShowError("Speichern fehlgeschlagen.");
return;
}
SelectListItem(
_lvSystems,
item =>
item.Tag is SonicSystemOption system
&& system.SonicConnectionId == systemId);
MessageBox.Show(
this,
"Gespeichert.",
"OK",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
private async Task AddContainerAsync()
{
int? preferred = null;
if (_lvSystems.SelectedItems.Count > 0
&& _lvSystems.SelectedItems[0].Tag is SonicSystemOption system)
{
preferred = system.SonicConnectionId;
}
using AddContainerForm dialog = new(_coordinator, preferred);
if (dialog.ShowDialog(this) != DialogResult.OK
|| dialog.CreatedSonicContainerId is not int createdId)
{
return;
}
await AfterContainerSavedAsync(createdId);
}
private async Task EditSelectedContainerAsync()
{
if (_lvContainers.SelectedItems.Count == 0
|| _lvContainers.SelectedItems[0].Tag is not ContainerOption container)
{
MessageBox.Show(
this,
"Bitte auswä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);
}
private async Task DeleteSelectedContainerAsync()
{
if (_lvContainers.SelectedItems.Count == 0
|| _lvContainers.SelectedItems[0].Tag is not ContainerOption container)
{
MessageBox.Show(
this,
"Bitte auswählen.",
"Container löschen",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
DialogResult confirm = MessageBox.Show(
this,
"Container entfernen?",
"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,
"Entfernt.",
"Container gelöscht",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
private async Task AfterContainerSavedAsync(int containerId)
{
await LoadContainersListAsync();
await LoadSystemsListAsync();
await RefreshAllAsync();
bool visible = _lvContainers.Items
.Cast<ListViewItem>()
.Any(item =>
item.Tag is ContainerOption container
&& container.SonicContainerId == containerId);
if (!visible)
{
ShowError("Speichern fehlgeschlagen.");
return;
}
SelectListItem(
_lvContainers,
item =>
item.Tag is ContainerOption container
&& container.SonicContainerId == containerId);
MessageBox.Show(
this,
"Gespeichert.",
"OK",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
private async Task AddPathAsync()
{
int? preferred = null;
if (_lvContainers.SelectedItems.Count > 0
&& _lvContainers.SelectedItems[0].Tag is ContainerOption container)
{
preferred = container.SonicContainerId;
}
using AddTargetPathForm dialog = new(_coordinator, preferred);
if (dialog.ShowDialog(this) != DialogResult.OK
|| dialog.CreatedCertificateTargetId is not int createdId)
{
return;
}
await AfterPathSavedAsync(createdId);
}
private async Task EditSelectedPathAsync()
{
if (_lvPaths.SelectedItems.Count == 0
|| _lvPaths.SelectedItems[0].Tag is not CertificateTargetOption target)
{
MessageBox.Show(
this,
"Bitte auswä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);
}
private async Task AfterPathSavedAsync(int pathId)
{
await LoadPathsListAsync();
await RefreshAllAsync();
bool visible = _lvPaths.Items
.Cast<ListViewItem>()
.Any(item =>
item.Tag is CertificateTargetOption target
&& target.CertificateTargetId == pathId);
if (!visible)
{
ShowError("Speichern fehlgeschlagen.");
return;
}
SelectListItem(
_lvPaths,
item =>
item.Tag is CertificateTargetOption target
&& target.CertificateTargetId == pathId);
MessageBox.Show(
this,
"Gespeichert.",
"OK",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
private async Task DeleteSelectedPathAsync()
{
if (_lvPaths.SelectedItems.Count == 0
|| _lvPaths.SelectedItems[0].Tag is not CertificateTargetOption target)
{
MessageBox.Show(
this,
"Bitte auswählen.",
"Pfad löschen",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
DialogResult confirm = MessageBox.Show(
this,
"Pfad entfernen?",
"Pfad löschen",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
if (confirm != DialogResult.Yes)
{
return;
}
try
{
await _coordinator.DeactivateCertificateTargetAsync(
target.CertificateTargetId);
await LoadPathsListAsync();
await RefreshAllAsync();
MessageBox.Show(
this,
"Entfernt.",
"Pfad gelöscht",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
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()
{
LocalSetupSelection? previousSelection =
_coordinator.LoadSelection();
_systems = await _coordinator.LoadSystemsAsync();
_cmbSystem.Items.Clear();
_cmbCredential.Items.Clear();
foreach (SonicSystemOption system in _systems)
{
_cmbSystem.Items.Add(system);
}
if (_systems.Count == 0)
{
_lblSystemDetails.Text =
"Kein System vorhanden. Lege zuerst unter „Systeme“ eines an.";
return;
}
int selectedIndex = 0;
if (previousSelection is not null)
{
for (int index = 0; index < _systems.Count; index++)
{
if (_systems[index].SonicConnectionId
== previousSelection.SonicConnectionId)
{
selectedIndex = index;
break;
}
}
}
_cmbSystem.SelectedIndex = selectedIndex;
}
private async Task AddCredentialAsync()
{
if (_cmbSystem.SelectedItem is not SonicSystemOption system)
{
ShowWarning("Bitte auswählen.");
return;
}
using AddCredentialProfileForm dialog =
new(_coordinator, system);
if (dialog.ShowDialog(this) != DialogResult.OK
|| dialog.CreatedProfile is null)
{
return;
}
await SystemChangedAsync();
SelectCredential(dialog.CreatedProfile.SonicCredentialId);
MessageBox.Show(
this,
"Profil geladen.",
"Benutzerprofil",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
private async Task EditCredentialAsync()
{
if (_cmbSystem.SelectedItem is not SonicSystemOption system)
{
ShowWarning("Bitte System wählen.");
return;
}
if (_cmbCredential.SelectedItem is not SonicCredentialProfile profile)
{
ShowWarning("Bitte Profil wählen.");
return;
}
using AddCredentialProfileForm dialog =
new(_coordinator, system, profile);
if (dialog.ShowDialog(this) != DialogResult.OK
|| dialog.CreatedProfile is null)
{
return;
}
await SystemChangedAsync();
SelectCredential(dialog.CreatedProfile.SonicCredentialId);
MessageBox.Show(
this,
"Profil aktualisiert.",
"Benutzerprofil",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
private void SelectCredential(int sonicCredentialId)
{
for (int index = 0; index < _cmbCredential.Items.Count; index++)
{
if (_cmbCredential.Items[index] is SonicCredentialProfile profile
&& profile.SonicCredentialId == sonicCredentialId)
{
_cmbCredential.SelectedIndex = index;
break;
}
}
}
private async Task SystemChangedAsync()
{
if (_cmbSystem.SelectedItem is not SonicSystemOption system)
{
return;
}
_lblSystemDetails.Text =
$"Domain: {system.DomainName} · {system.ConnectionUrl}";
_profiles =
await _coordinator.LoadCredentialsAsync(
system.SonicConnectionId);
_cmbCredential.Items.Clear();
foreach (SonicCredentialProfile profile in _profiles)
{
_cmbCredential.Items.Add(profile);
}
if (_profiles.Count == 0)
{
_lblSystemDetails.Text +=
Environment.NewLine
+ "Noch kein Benutzerprofil — bitte hinzufügen.";
_btnSaveSelection.Enabled = false;
_btnEditCredential.Enabled = false;
return;
}
_btnEditCredential.Enabled = true;
_btnSaveSelection.Enabled = true;
LocalSetupSelection? selection = _coordinator.LoadSelection();
int selectedIndex = 0;
if (selection is not null)
{
for (int index = 0; index < _profiles.Count; index++)
{
if (_profiles[index].SonicCredentialId
== selection.SonicCredentialId)
{
selectedIndex = index;
break;
}
}
}
else
{
selectedIndex = _profiles
.Select((profile, index) => new { profile, index })
.Where(item => item.profile.IsDefault)
.Select(item => item.index)
.FirstOrDefault();
}
_cmbCredential.SelectedIndex = selectedIndex;
}
private async Task SaveSelectionAsync()
{
if (_cmbSystem.SelectedItem is not SonicSystemOption system)
{
ShowWarning("Bitte System wählen.");
return;
}
if (_cmbCredential.SelectedItem is not SonicCredentialProfile profile)
{
ShowWarning("Bitte Profil wählen.");
return;
}
_coordinator.SaveSelection(system, profile);
MessageBox.Show(
this,
"Gespeichert.",
"Einstellungen",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
await RefreshAllAsync();
}
private async Task ImportCertificateAsync()
{
using OpenFileDialog dialog = new()
{
Title = "Always-Encrypted-PFX auswählen",
Filter = "PFX-Zertifikat (*.pfx)|*.pfx",
CheckFileExists = true
};
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
string? password = PromptForPassword();
if (password is null)
{
return;
}
try
{
_coordinator.CertificateService.ImportPfx(
dialog.FileName,
password);
await RefreshAllAsync();
}
finally
{
password = string.Empty;
}
}
private async Task CloseAsync()
{
if (_isSettingsMode)
{
// Katalog-Änderungen (Pfade/Container) sollen die Hauptansicht
// immer aktualisieren unabhängig davon, ob das Profil schon „ready“ ist.
_ = await _coordinator.CheckAsync();
DialogResult = DialogResult.OK;
}
else
{
DialogResult = DialogResult.Cancel;
}
Close();
}
private static string? PromptForPassword()
{
using Form form = new()
{
Text = "PFX-Kennwort",
Size = new Size(390, 170),
StartPosition = FormStartPosition.CenterParent,
FormBorderStyle = FormBorderStyle.FixedDialog,
BackColor = SettingsUi.Background,
ForeColor = SettingsUi.Text
};
TextBox textBox = SettingsUi.CreateTextBox();
textBox.Location = new Point(20, 40);
textBox.Width = 330;
textBox.UseSystemPasswordChar = true;
Button ok = SettingsUi.CreatePrimaryButton("OK");
ok.Location = new Point(160, 85);
ok.Size = new Size(90, 34);
ok.DialogResult = DialogResult.OK;
Button cancel = SettingsUi.CreateGhostButton("Abbrechen");
cancel.Location = new Point(260, 85);
cancel.Size = new Size(90, 34);
cancel.DialogResult = DialogResult.Cancel;
form.Controls.Add(textBox);
form.Controls.Add(ok);
form.Controls.Add(cancel);
form.AcceptButton = ok;
form.CancelButton = cancel;
return form.ShowDialog() == DialogResult.OK
? textBox.Text
: null;
}
private void SetRunning(bool running)
{
_running = running;
UseWaitCursor = running;
}
private void ShowWarning(string message)
{
MessageBox.Show(
this,
message,
"Einstellungen",
MessageBoxButtons.OK,
MessageBoxIcon.Warning);
}
private void ShowError(string message)
{
MessageBox.Show(
this,
message,
"Einstellungen",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
private static void SetStatus(
Label label,
string category,
bool success,
string message)
{
label.Text =
$"{(success ? "" : "")} {category}\n {message}";
label.ForeColor = success
? SettingsUi.Green
: SettingsUi.Red;
}
}