Files
123123/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs
T

1507 lines
45 KiB
C#
Raw 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 Label _lblEnvironmentBadge = 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 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)
};
_lblEnvironmentBadge = new Label
{
Text = _coordinator.Settings.EnvironmentCode,
Location = new Point(22, 95),
AutoSize = true,
Padding = new Padding(10, 4, 10, 4),
BackColor = Color.FromArgb(40, 208, 171, 57),
ForeColor = SettingsUi.Gold,
Font = new Font("Segoe UI Semibold", 9f, FontStyle.Bold)
};
_sidebar.Controls.Add(brand);
_sidebar.Controls.Add(brandSub);
_sidebar.Controls.Add(_lblEnvironmentBadge);
string[] navItems =
[
"Prüfung",
"Systeme",
"Container",
"Pfade",
"Profile"
];
int y = 150;
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", 80);
_lvContainers.Columns.Add("Container", 180);
_lvContainers.Columns.Add("Anzeige", 160);
_lvContainers.Columns.Add("System", 160);
_lvContainers.Columns.Add("Timeout", 90);
_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();
_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(_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(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 zuerst einen Zertifikat-Pfad in der Liste wä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.Message + "\n\n" + target.FullTargetPath,
"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";
}
return result.Success ? "fehlt" : "n/a";
}
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, 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();
bool visible = _lvSystems.Items
.Cast<ListViewItem>()
.Any(item =>
item.Tag is SonicSystemOption system
&& system.SonicConnectionId == systemId);
if (!visible)
{
ShowError(
$"{(created ? "Anlage" : "Update")}-Prüfung fehlgeschlagen: "
+ $"System-Id {systemId} ist nach dem Speichern nicht in der Liste.");
return;
}
SelectListItem(
_lvSystems,
item =>
item.Tag is SonicSystemOption system
&& system.SonicConnectionId == systemId);
MessageBox.Show(
this,
created
? $"System wurde gespeichert und geprüft (Id {systemId})."
: $"System wurde aktualisiert und geprüft (Id {systemId}).",
"Prüfung 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, 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();
bool visible = _lvContainers.Items
.Cast<ListViewItem>()
.Any(item =>
item.Tag is ContainerOption container
&& container.SonicContainerId == containerId);
if (!visible)
{
ShowError(
$"{(created ? "Anlage" : "Update")}-Prüfung fehlgeschlagen: "
+ $"Container-Id {containerId} ist nach dem Speichern nicht in der Liste.");
return;
}
SelectListItem(
_lvContainers,
item =>
item.Tag is ContainerOption container
&& container.SonicContainerId == containerId);
MessageBox.Show(
this,
created
? $"Container wurde gespeichert und geprüft (Id {containerId})."
: $"Container wurde aktualisiert und geprüft (Id {containerId}).",
"Prüfung 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, 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();
bool visible = _lvPaths.Items
.Cast<ListViewItem>()
.Any(item =>
item.Tag is CertificateTargetOption target
&& target.CertificateTargetId == pathId);
if (!visible)
{
ShowError(
$"{(created ? "Anlage" : "Update")}-Prüfung fehlgeschlagen: "
+ $"Pfad-Id {pathId} ist nach dem Speichern nicht in der Liste.");
return;
}
SelectListItem(
_lvPaths,
item =>
item.Tag is CertificateTargetOption target
&& target.CertificateTargetId == pathId);
MessageBox.Show(
this,
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);
}
private async Task DeleteSelectedPathAsync()
{
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 löschen",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
DialogResult confirm = MessageBox.Show(
this,
"Zertifikat-Pfad wirklich aus der Konfiguration entfernen?\n\n"
+ $"{target.FullTargetPath}\n\n"
+ "Die Datei auf dem Server bleibt unverändert. "
+ "Der Eintrag wird in SQL nur deaktiviert (IsActive=0).",
"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,
$"Pfad Id {target.CertificateTargetId} wurde deaktiviert.",
"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 zuerst ein Sonic-System auswählen.");
return;
}
using AddCredentialProfileForm dialog =
new(_coordinator, system);
if (dialog.ShowDialog(this) != DialogResult.OK
|| dialog.CreatedProfile is null)
{
return;
}
await SystemChangedAsync();
for (int index = 0; index < _cmbCredential.Items.Count; index++)
{
if (_cmbCredential.Items[index] is SonicCredentialProfile profile
&& profile.SonicCredentialId
== dialog.CreatedProfile.SonicCredentialId)
{
_cmbCredential.SelectedIndex = index;
break;
}
}
MessageBox.Show(
this,
"Profil geladen. Jetzt „Auswahl übernehmen“ klicken.",
"Benutzerprofil",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
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;
return;
}
_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 ein Sonic-System auswählen.");
return;
}
if (_cmbCredential.SelectedItem is not SonicCredentialProfile profile)
{
ShowWarning("Bitte ein Benutzerprofil auswählen.");
return;
}
_coordinator.SaveSelection(system, profile);
MessageBox.Show(
this,
"Auswahl 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;
}
}