2063 lines
68 KiB
C#
2063 lines
68 KiB
C#
using System;
|
||
using System.Security.Cryptography;
|
||
using System.Security.Cryptography.X509Certificates;
|
||
using ZA.CoreService.ESBCertificateManager.Configuration;
|
||
using ZA.CoreService.ESBCertificateManager.Models;
|
||
using ZA.CoreService.ESBCertificateManager.Services;
|
||
using Microsoft.Data.SqlClient;
|
||
|
||
namespace ZA.CoreService.ESBCertificateManager
|
||
{
|
||
public partial class Form1 : Form
|
||
{
|
||
|
||
// Große Hintergrundflächen
|
||
private readonly Color BackgroundColor = Color.FromArgb(10, 18, 34); // #0A1222
|
||
private readonly Color SidebarColor = Color.FromArgb(7, 23, 45); // #07172D
|
||
private readonly Color CardColor = Color.FromArgb(16, 38, 65); // #102641
|
||
private readonly Color CardHoverColor = Color.FromArgb(23, 55, 94); // #17375E
|
||
private readonly Color BorderColor = Color.FromArgb(36, 74, 117); // #244A75
|
||
|
||
// ZIEHL-ABEGG-nahe Markenakzente
|
||
private readonly Color BlueColor = Color.FromArgb(0, 110, 182); // #006EB6
|
||
private readonly Color BrightBlueColor = Color.FromArgb(0, 139, 210); // #008BD2
|
||
private readonly Color GoldColor = Color.FromArgb(208, 171, 57); // #D0AB39
|
||
private readonly Color LightGoldColor = Color.FromArgb(226, 196, 93); // #E2C45D
|
||
|
||
// Schrift und Statusfarben
|
||
private readonly Color TextColor = Color.FromArgb(241, 245, 249); // #F1F5F9
|
||
private readonly Color MutedTextColor = Color.FromArgb(169, 184, 200); // #A9B8C8
|
||
private readonly Color GreenColor = Color.FromArgb(60, 203, 127); // #3CCB7F
|
||
private readonly Color RedColor = Color.FromArgb(239, 106, 106); // #EF6A6A
|
||
|
||
private readonly AppSettings _settings;
|
||
private DeploymentOrchestrator? _orchestrator;
|
||
private readonly SonicConnectionRepository _sonicConnectionRepository;
|
||
private readonly DeploymentTargetRepository _deploymentTargetRepository;
|
||
|
||
private IReadOnlyList<DatabaseSonicConnection>
|
||
_databaseSonicConnections = [];
|
||
|
||
private CertificateInfo? _loadedCertificateInfo;
|
||
private IReadOnlyList<DeploymentTarget> _loadedTargets = [];
|
||
private bool _isOperationRunning;
|
||
private CancellationTokenSource? _runCts;
|
||
|
||
private Label lblStatus = null!;
|
||
private Label lblStatusDot = null!;
|
||
private TextBox txtCertificatePath = null!;
|
||
private DataGridView dgvTargets = null!;
|
||
private ComboBox cmbSonicConnection = null!;
|
||
private Label lblSonicStatus = null!;
|
||
|
||
private Panel navProgressPanel = null!;
|
||
|
||
private Panel titleBar = null!;
|
||
private Button btnMinimize = null!;
|
||
private Button btnMaximize = null!;
|
||
private Button btnClose = null!;
|
||
private Button btnValidate = null!;
|
||
private Button btnDeploy = null!;
|
||
private Button btnRestartOnly = null!;
|
||
private Button btnCancelRun = null!;
|
||
|
||
private bool isMaximized = false;
|
||
private Point dragStartPoint;
|
||
|
||
private Label lblCertificateSubject = null!;
|
||
private Label lblCertificateIssuer = null!;
|
||
private Label lblCertificateExpiry = null!;
|
||
private Label lblCertificateFingerprint = null!;
|
||
private Label lblCertificateValidity = null!;
|
||
|
||
private Button btnSelectFile = null!;
|
||
private bool isCertificateFileLoaded;
|
||
private int nvbarlaststate = 1;
|
||
|
||
public Form1()
|
||
{
|
||
InitializeComponent();
|
||
|
||
_settings = AppSettingsLoader.Load();
|
||
|
||
|
||
|
||
|
||
_sonicConnectionRepository = new SonicConnectionRepository(
|
||
_settings.Database.ConnectionString);
|
||
|
||
_deploymentTargetRepository = new DeploymentTargetRepository(
|
||
_settings.Database.ConnectionString);
|
||
|
||
BuildDesign();
|
||
|
||
Shown += async (_, _) => await InitializeApplicationAsync();
|
||
}
|
||
private Button CreateWindowButton(string text)
|
||
{
|
||
return new Button
|
||
{
|
||
Text = text,
|
||
Width = 46,
|
||
Height = 34,
|
||
FlatStyle = FlatStyle.Flat,
|
||
FlatAppearance =
|
||
{
|
||
BorderSize = 0,
|
||
MouseOverBackColor = CardHoverColor
|
||
},
|
||
BackColor = SidebarColor,
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 11),
|
||
Cursor = Cursors.Hand,
|
||
TabStop = false
|
||
};
|
||
}
|
||
|
||
private void ToggleMaximizeWindow()
|
||
{
|
||
if (isMaximized)
|
||
{
|
||
WindowState = FormWindowState.Normal;
|
||
isMaximized = false;
|
||
}
|
||
else
|
||
{
|
||
WindowState = FormWindowState.Maximized;
|
||
isMaximized = true;
|
||
}
|
||
}
|
||
|
||
private void TitleBar_MouseDown(object? sender, MouseEventArgs e)
|
||
{
|
||
if (e.Button == MouseButtons.Left)
|
||
{
|
||
dragStartPoint = e.Location;
|
||
}
|
||
}
|
||
private async Task InitializeApplicationAsync()
|
||
{
|
||
|
||
SetStatus(
|
||
"Java- und Sonic-Runtime werden geprüft ...",
|
||
isError: false);
|
||
|
||
RuntimeEnvironmentValidator runtimeValidator = new();
|
||
|
||
RuntimeValidationResult runtimeResult =
|
||
await runtimeValidator.ValidateAsync(
|
||
_settings.Runtime);
|
||
|
||
if (!runtimeResult.IsValid)
|
||
{
|
||
string details = string.Join(
|
||
Environment.NewLine,
|
||
runtimeResult.Issues.Select(
|
||
issue => "• " + issue));
|
||
|
||
throw new InvalidOperationException(
|
||
"Die lokale Runtime-Konfiguration ist unvollständig:" +
|
||
Environment.NewLine +
|
||
Environment.NewLine +
|
||
details);
|
||
}
|
||
try
|
||
{
|
||
_databaseSonicConnections =
|
||
await _sonicConnectionRepository.GetActiveAsync();
|
||
|
||
List<SonicConnection> runtimeConnections =
|
||
_databaseSonicConnections
|
||
.Where(connection => connection.HasCredentials)
|
||
.Select(connection =>
|
||
connection.ToRuntimeConnection(
|
||
_settings.Runtime))
|
||
.ToList();
|
||
|
||
_orchestrator = new DeploymentOrchestrator(
|
||
_settings,
|
||
runtimeConnections);
|
||
|
||
BindDatabaseSonicConnections();
|
||
|
||
await LoadTargetsAsync();
|
||
|
||
if (_databaseSonicConnections.Count == 0)
|
||
{
|
||
SetStatus(
|
||
"SQL-Zugriff erfolgreich. " +
|
||
"Noch keine aktiven Sonic-Verbindungen in SQL. " +
|
||
"Lokale Testziele wurden geladen.",
|
||
isError: false);
|
||
|
||
return;
|
||
}
|
||
|
||
SetStatus(
|
||
$"{_databaseSonicConnections.Count} aktive Sonic-Verbindung(en) " +
|
||
$"aus SQL und {_loadedTargets.Count} lokale Testziele geladen.",
|
||
isError: false);
|
||
}
|
||
catch (SqlException ex)
|
||
{
|
||
_databaseSonicConnections = [];
|
||
|
||
SetStatus(
|
||
$"SQL-Zugriff fehlgeschlagen: {ex.Message}",
|
||
isError: true);
|
||
|
||
MessageBox.Show(
|
||
this,
|
||
ex.Message,
|
||
"SQL-Zugriff fehlgeschlagen",
|
||
MessageBoxButtons.OK,
|
||
MessageBoxIcon.Error);
|
||
}
|
||
catch (InvalidOperationException ex)
|
||
{
|
||
_databaseSonicConnections = [];
|
||
|
||
SetStatus(
|
||
$"Konfiguration ungültig: {ex.Message}",
|
||
isError: true);
|
||
|
||
MessageBox.Show(
|
||
this,
|
||
ex.Message,
|
||
"Konfiguration ungültig",
|
||
MessageBoxButtons.OK,
|
||
MessageBoxIcon.Warning);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_databaseSonicConnections = [];
|
||
|
||
SetStatus(
|
||
$"Initialisierung fehlgeschlagen: {ex.Message}",
|
||
isError: true);
|
||
|
||
MessageBox.Show(
|
||
this,
|
||
ex.Message,
|
||
"Initialisierung fehlgeschlagen",
|
||
MessageBoxButtons.OK,
|
||
MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
private void TitleBar_MouseMove(object? sender, MouseEventArgs e)
|
||
{
|
||
if (e.Button == MouseButtons.Left)
|
||
{
|
||
Left += e.X - dragStartPoint.X;
|
||
Top += e.Y - dragStartPoint.Y;
|
||
}
|
||
}
|
||
private Panel BuildTitleBar()
|
||
{
|
||
Panel panel = new Panel
|
||
{
|
||
Height = 34,
|
||
BackColor = SidebarColor
|
||
};
|
||
|
||
Label appIcon = new Label
|
||
{
|
||
Text = "◆",
|
||
ForeColor = GoldColor,
|
||
Font = new Font("Segoe UI Symbol", 12, FontStyle.Bold),
|
||
AutoSize = true,
|
||
Location = new Point(12, 8)
|
||
};
|
||
|
||
Label appName = new Label
|
||
{
|
||
Text = "ESB Certificate Manager",
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 9),
|
||
AutoSize = true,
|
||
Location = new Point(31, 9)
|
||
};
|
||
|
||
btnClose = CreateWindowButton("×");
|
||
btnMaximize = CreateWindowButton("□");
|
||
btnMinimize = CreateWindowButton("−");
|
||
|
||
btnClose.ForeColor = TextColor;
|
||
btnClose.FlatAppearance.MouseOverBackColor = Color.FromArgb(190, 35, 45);
|
||
|
||
btnClose.Click += (_, _) => Close();
|
||
|
||
btnMinimize.Click += (_, _) =>
|
||
{
|
||
WindowState = FormWindowState.Minimized;
|
||
};
|
||
|
||
btnMaximize.Click += (_, _) =>
|
||
{
|
||
ToggleMaximizeWindow();
|
||
};
|
||
|
||
panel.Resize += (_, _) =>
|
||
{
|
||
btnClose.Left = panel.Width - btnClose.Width;
|
||
btnMaximize.Left = btnClose.Left - btnMaximize.Width;
|
||
btnMinimize.Left = btnMaximize.Left - btnMinimize.Width;
|
||
};
|
||
|
||
panel.MouseDown += TitleBar_MouseDown;
|
||
panel.MouseMove += TitleBar_MouseMove;
|
||
appIcon.MouseDown += TitleBar_MouseDown;
|
||
appIcon.MouseMove += TitleBar_MouseMove;
|
||
appName.MouseDown += TitleBar_MouseDown;
|
||
appName.MouseMove += TitleBar_MouseMove;
|
||
|
||
panel.Controls.Add(appIcon);
|
||
panel.Controls.Add(appName);
|
||
panel.Controls.Add(btnMinimize);
|
||
panel.Controls.Add(btnMaximize);
|
||
panel.Controls.Add(btnClose);
|
||
|
||
return panel;
|
||
}
|
||
private void BuildDesign()
|
||
{
|
||
Text = "ESB Certificate Manager";
|
||
StartPosition = FormStartPosition.CenterScreen;
|
||
|
||
MinimumSize = new Size(1100, 700);
|
||
Size = new Size(1400, 850);
|
||
|
||
BackColor = BackgroundColor;
|
||
Font = new Font("Segoe UI", 10);
|
||
ForeColor = TextColor;
|
||
|
||
// Entfernt die weiße Windows-Standardtitelleiste.
|
||
FormBorderStyle = FormBorderStyle.None;
|
||
|
||
// Eigene dunkle Titelleiste.
|
||
titleBar = BuildTitleBar();
|
||
titleBar.Dock = DockStyle.Top;
|
||
Controls.Add(titleBar);
|
||
|
||
Panel sidebar = new Panel
|
||
{
|
||
Dock = DockStyle.Left,
|
||
Width = 205,
|
||
BackColor = SidebarColor
|
||
};
|
||
|
||
Panel content = new Panel
|
||
{
|
||
Dock = DockStyle.Fill,
|
||
BackColor = BackgroundColor,
|
||
Padding = new Padding(38, 48, 38, 34),
|
||
AutoScroll = true
|
||
};
|
||
|
||
// Wichtig für Docking:
|
||
// Erst der Fill-Bereich, dann die feste Sidebar.
|
||
Controls.Add(content);
|
||
Controls.Add(sidebar);
|
||
|
||
// Dezente vertikale Linie rechts an der Sidebar.
|
||
Panel sidebarBorder = new Panel
|
||
{
|
||
Dock = DockStyle.Right,
|
||
Width = 1,
|
||
BackColor = BorderColor
|
||
};
|
||
|
||
sidebar.Controls.Add(sidebarBorder);
|
||
|
||
BuildSidebar(sidebar);
|
||
BuildContent(content);
|
||
}
|
||
|
||
private void BuildSidebar(Panel sidebar)
|
||
{
|
||
|
||
// -------------------------
|
||
// 1. Logo / Produktbereich
|
||
// -------------------------
|
||
Panel logoPanel = new Panel
|
||
{
|
||
Dock = DockStyle.Top,
|
||
Height = 165,
|
||
Padding = new Padding(18, 18, 18, 8)
|
||
};
|
||
|
||
PictureBox pictureLogo = new PictureBox
|
||
{
|
||
Location = new Point(18, 14),
|
||
Size = new Size(165, 58),
|
||
SizeMode = PictureBoxSizeMode.Zoom,
|
||
BackColor = Color.Transparent
|
||
};
|
||
|
||
string logoPath = Path.Combine(
|
||
Application.StartupPath,
|
||
"Assets",
|
||
"logo.png");
|
||
|
||
if (File.Exists(logoPath))
|
||
{
|
||
pictureLogo.Image = Image.FromFile(logoPath);
|
||
}
|
||
|
||
Label logoSubtitle = new Label
|
||
{
|
||
Text = "ESB CERTIFICATE MANAGER",
|
||
ForeColor = TextColor,
|
||
Font = new Font("Segoe UI", 8, FontStyle.Bold),
|
||
AutoSize = true,
|
||
Location = new Point(18, 88)
|
||
};
|
||
|
||
|
||
Panel logoSeparator = new Panel
|
||
{
|
||
BackColor = BorderColor,
|
||
Location = new Point(22, 138),
|
||
Size = new Size(168, 1)
|
||
};
|
||
|
||
logoPanel.Controls.Add(pictureLogo);
|
||
logoPanel.Controls.Add(logoSubtitle);
|
||
logoPanel.Controls.Add(logoSeparator);
|
||
|
||
// ------------------------------------
|
||
// 2. Linker Ablauf: keine Navigation,
|
||
// sondern Fortschritt im Deployment
|
||
// ------------------------------------
|
||
Panel progressPanel = new Panel
|
||
{
|
||
Dock = DockStyle.Top,
|
||
Height = 360,
|
||
Padding = new Padding(18, 8, 18, 8)
|
||
};
|
||
navProgressPanel = progressPanel;
|
||
|
||
Label progressTitle = new Label
|
||
{
|
||
Text = "DEPLOYMENT-ABLAUF",
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 7.5f, FontStyle.Bold),
|
||
AutoSize = true,
|
||
Location = new Point(18, 12)
|
||
};
|
||
|
||
AddStep(
|
||
progressPanel,
|
||
y: 48,
|
||
number: "1",
|
||
title: "Zertifikat",
|
||
description: "Datei auswählen",
|
||
isActive: true,
|
||
hasNextStep: true);
|
||
|
||
AddStep(
|
||
progressPanel,
|
||
y: 116,
|
||
number: "2",
|
||
title: "Ziele",
|
||
description: "Systeme auswählen",
|
||
isActive: false,
|
||
hasNextStep: true);
|
||
|
||
AddStep(
|
||
progressPanel,
|
||
y: 184,
|
||
number: "3",
|
||
title: "Bereitstellen",
|
||
description: "Kopieren & Neustart",
|
||
isActive: false,
|
||
hasNextStep: true);
|
||
|
||
AddStep(
|
||
progressPanel,
|
||
y: 252,
|
||
number: "4",
|
||
title: "TLS-Prüfung",
|
||
description: "Zertifikat bestätigen",
|
||
isActive: false,
|
||
hasNextStep: false);
|
||
|
||
progressPanel.Controls.Add(progressTitle);
|
||
|
||
// -----------------------------
|
||
// 3. Footer unten: Version
|
||
// -----------------------------
|
||
Panel footerPanel = new Panel
|
||
{
|
||
Dock = DockStyle.Bottom,
|
||
Height = 70,
|
||
Padding = new Padding(18, 8, 12, 8)
|
||
};
|
||
|
||
Panel footerSeparator = new Panel
|
||
{
|
||
Dock = DockStyle.Top,
|
||
Height = 1,
|
||
BackColor = BorderColor
|
||
};
|
||
|
||
Label versionLabel = new Label
|
||
{
|
||
Text = "INTERNAL TOOL",
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 7.5f, FontStyle.Bold),
|
||
AutoSize = true,
|
||
Location = new Point(18, 17)
|
||
};
|
||
|
||
Label versionNumber = new Label
|
||
{
|
||
Text = "Version 0.1.0",
|
||
ForeColor = BlueColor,
|
||
Font = new Font("Segoe UI", 8),
|
||
AutoSize = true,
|
||
Location = new Point(18, 37)
|
||
};
|
||
|
||
footerPanel.Controls.Add(footerSeparator);
|
||
footerPanel.Controls.Add(versionLabel);
|
||
footerPanel.Controls.Add(versionNumber);
|
||
|
||
// Dock-Reihenfolge: Bottom, Top, Top
|
||
sidebar.Controls.Add(footerPanel);
|
||
sidebar.Controls.Add(progressPanel);
|
||
sidebar.Controls.Add(logoPanel);
|
||
}
|
||
|
||
private void AddStep(
|
||
Panel parent,
|
||
int y,
|
||
string number,
|
||
string title,
|
||
string description,
|
||
bool isActive,
|
||
bool hasNextStep)
|
||
{
|
||
Color stepColor = isActive ? GoldColor : MutedTextColor;
|
||
Color titleColor = isActive ? TextColor : MutedTextColor;
|
||
|
||
Label circle = new Label
|
||
{
|
||
Name = $"stepCircle{number}",
|
||
Text = number,
|
||
Location = new Point(18, y),
|
||
Size = new Size(25, 25),
|
||
TextAlign = ContentAlignment.MiddleCenter,
|
||
BackColor = isActive ? GoldColor : Color.FromArgb(25, 48, 76),
|
||
ForeColor = isActive ? SidebarColor : MutedTextColor,
|
||
Font = new Font("Segoe UI", 8, FontStyle.Bold)
|
||
};
|
||
|
||
Label titleLabel = new Label
|
||
{
|
||
Name = $"stepTitle{number}",
|
||
Text = title,
|
||
ForeColor = titleColor,
|
||
Font = new Font("Segoe UI", 9.5f, FontStyle.Bold),
|
||
AutoSize = true,
|
||
Location = new Point(56, y - 1)
|
||
};
|
||
|
||
Label descriptionLabel = new Label
|
||
{
|
||
Name = $"stepDescription{number}",
|
||
Text = description,
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 8),
|
||
AutoSize = true,
|
||
Location = new Point(56, y + 16)
|
||
};
|
||
|
||
parent.Controls.Add(circle);
|
||
parent.Controls.Add(titleLabel);
|
||
parent.Controls.Add(descriptionLabel);
|
||
|
||
if (hasNextStep)
|
||
{
|
||
Panel line = new Panel
|
||
{
|
||
BackColor = BorderColor,
|
||
Location = new Point(30, y + 26),
|
||
Size = new Size(1, 40)
|
||
};
|
||
|
||
parent.Controls.Add(line);
|
||
line.SendToBack();
|
||
}
|
||
}
|
||
private void UpdateToNextStep(int state)
|
||
{
|
||
|
||
Control[] foundCircles = navProgressPanel.Controls.Find($"stepCircle{state}", true);
|
||
if (foundCircles.Length == 0) return;
|
||
Control[] foundTitels = navProgressPanel.Controls.Find($"stepTitle{state}", true);
|
||
if (foundTitels.Length == 0) return;
|
||
Label titel = (Label)foundTitels[0];
|
||
Label circle = (Label)foundCircles[0];
|
||
|
||
|
||
Control[] foundlastTitels = navProgressPanel.Controls.Find($"stepTitle{nvbarlaststate}", true);
|
||
if (foundlastTitels.Length == 0) return;
|
||
Label lasttitel = (Label)foundlastTitels[0];
|
||
if (nvbarlaststate > state)
|
||
{
|
||
Control[] foundlastCircles = navProgressPanel.Controls.Find($"stepCircle{nvbarlaststate}", true);
|
||
if (foundlastCircles.Length == 0) return;
|
||
Label lastcircle = (Label)foundlastCircles[0];
|
||
lastcircle.BackColor = Color.FromArgb(25, 48, 76);
|
||
lastcircle.ForeColor = MutedTextColor;
|
||
lasttitel.ForeColor = MutedTextColor;
|
||
}
|
||
else
|
||
{
|
||
lasttitel.ForeColor = MutedTextColor;
|
||
}
|
||
|
||
|
||
circle.BackColor = GoldColor;
|
||
circle.ForeColor = SidebarColor;
|
||
titel.ForeColor = TextColor;
|
||
nvbarlaststate = state;
|
||
}
|
||
|
||
private Button CreateNavButton(string text, bool isActive)
|
||
{
|
||
Button button = new Button
|
||
{
|
||
Text = text,
|
||
Height = 43,
|
||
Dock = DockStyle.Top,
|
||
FlatStyle = FlatStyle.Flat,
|
||
FlatAppearance =
|
||
{
|
||
BorderSize = 0,
|
||
MouseOverBackColor = Color.FromArgb(37, 52, 74)
|
||
},
|
||
BackColor = isActive ? Color.FromArgb(35, 58, 92) : SidebarColor,
|
||
ForeColor = isActive ? TextColor : MutedTextColor,
|
||
Font = new Font("Segoe UI", 10, isActive ? FontStyle.Bold : FontStyle.Regular),
|
||
TextAlign = ContentAlignment.MiddleLeft,
|
||
Padding = new Padding(14, 0, 0, 0),
|
||
Cursor = Cursors.Hand
|
||
};
|
||
|
||
button.Click += (_, _) =>
|
||
{
|
||
SetStatus($"Navigation gewählt: {text.Trim()}", isError: false);
|
||
};
|
||
|
||
return button;
|
||
}
|
||
|
||
private void BuildContent(Panel content)
|
||
{
|
||
// Status unten zuerst hinzufügen, weil Dock-Reihenfolge relevant ist
|
||
Panel statusBar = BuildStatusBar();
|
||
statusBar.Dock = DockStyle.Bottom;
|
||
content.Controls.Add(statusBar);
|
||
|
||
Panel actionPanel = BuildActionPanel();
|
||
actionPanel.Dock = DockStyle.Bottom;
|
||
actionPanel.Height = 65;
|
||
content.Controls.Add(actionPanel);
|
||
|
||
Panel targetsCard = BuildTargetsCard();
|
||
targetsCard.Dock = DockStyle.Top;
|
||
targetsCard.Height = 380;
|
||
targetsCard.Margin = new Padding(0, 18, 0, 0);
|
||
content.Controls.Add(targetsCard);
|
||
|
||
Panel topCards = BuildTopCards();
|
||
topCards.Dock = DockStyle.Top;
|
||
topCards.Height = 225;
|
||
topCards.Margin = new Padding(0, 20, 0, 0);
|
||
content.Controls.Add(topCards);
|
||
|
||
Panel header = BuildHeader();
|
||
header.Dock = DockStyle.Top;
|
||
header.Height = 87;
|
||
content.Controls.Add(header);
|
||
}
|
||
|
||
private Panel BuildHeader()
|
||
{
|
||
Panel header = new Panel
|
||
{
|
||
BackColor = BackgroundColor
|
||
};
|
||
|
||
Label title = new Label
|
||
{
|
||
Text = "Zertifikat bereitstellen",
|
||
ForeColor = TextColor,
|
||
Font = new Font("Segoe UI", 23, FontStyle.Bold),
|
||
AutoSize = true,
|
||
Location = new Point(0, 0)
|
||
};
|
||
|
||
Label subtitle = new Label
|
||
{
|
||
Text = "Verteile ein Zertifikat auf konfigurierte Ziele und prüfe anschließend die TLS-Verbindung.",
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 10),
|
||
AutoSize = true,
|
||
Location = new Point(3, 43)
|
||
};
|
||
|
||
Label environmentBadge = new Label
|
||
{
|
||
Text = " UMGEBUNG ",
|
||
ForeColor = GoldColor,
|
||
BackColor = Color.FromArgb(65, 53, 17),
|
||
Font = new Font("Segoe UI", 8, FontStyle.Bold),
|
||
AutoSize = true,
|
||
Padding = new Padding(7, 6, 7, 6),
|
||
Anchor = AnchorStyles.Top | AnchorStyles.Right
|
||
};
|
||
|
||
environmentBadge.Location = new Point(900, 9);
|
||
|
||
header.Resize += (_, _) =>
|
||
{
|
||
environmentBadge.Left = header.Width - environmentBadge.Width;
|
||
};
|
||
|
||
header.Controls.Add(title);
|
||
header.Controls.Add(subtitle);
|
||
header.Controls.Add(environmentBadge);
|
||
|
||
return header;
|
||
}
|
||
|
||
private Panel BuildTopCards()
|
||
{
|
||
Panel container = new Panel
|
||
{
|
||
BackColor = BackgroundColor
|
||
};
|
||
|
||
Panel sourceCard = CreateCard();
|
||
sourceCard.Dock = DockStyle.Left;
|
||
sourceCard.Width = 520;
|
||
|
||
Panel detailsCard = CreateCard();
|
||
detailsCard.Dock = DockStyle.Fill;
|
||
detailsCard.Margin = new Padding(18, 0, 0, 0);
|
||
|
||
container.Controls.Add(detailsCard);
|
||
container.Controls.Add(sourceCard);
|
||
|
||
BuildSourceCard(sourceCard);
|
||
BuildDetailsCard(detailsCard);
|
||
|
||
return container;
|
||
}
|
||
private void BuildSourceCard(Panel card)
|
||
{
|
||
Label step = CreateSmallTitle("SCHRITT 1");
|
||
step.Location = new Point(22, 18);
|
||
|
||
Label title = CreateCardTitle("Zertifikatsquelle");
|
||
title.Location = new Point(22, 40);
|
||
|
||
Label description = CreateMutedLabel(
|
||
"Wähle die Zertifikatsdatei aus der zentralen Ablage.");
|
||
description.Location = new Point(22, 73);
|
||
|
||
txtCertificatePath = new TextBox
|
||
{
|
||
Location = new Point(22, 112),
|
||
Size = new Size(320, 38),
|
||
BackColor = Color.FromArgb(20, 30, 48),
|
||
ForeColor = TextColor,
|
||
BorderStyle = BorderStyle.FixedSingle,
|
||
Font = new Font("Segoe UI", 9),
|
||
Text = string.Empty,
|
||
PlaceholderText = "Keine Datei ausgewählt",
|
||
ReadOnly = true
|
||
};
|
||
|
||
btnSelectFile = CreateSecondaryButton("Datei auswählen");
|
||
btnSelectFile.Location = new Point(354, 111);
|
||
btnSelectFile.Size = new Size(140, 39);
|
||
SetFileButtonState(fileLoaded: false);
|
||
|
||
btnSelectFile.Click += (_, _) =>
|
||
{
|
||
if (isCertificateFileLoaded)
|
||
{
|
||
ClearSelectedCertificate();
|
||
return;
|
||
}
|
||
|
||
TrySelectCertificateFile();
|
||
};
|
||
|
||
Label hint = new Label
|
||
{
|
||
Text = "Unterstützte Formate: .cer, .crt, .pem, .pfx",
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 8.5f),
|
||
AutoSize = true,
|
||
Location = new Point(22, 164)
|
||
};
|
||
|
||
card.Controls.Add(step);
|
||
card.Controls.Add(title);
|
||
card.Controls.Add(description);
|
||
card.Controls.Add(txtCertificatePath);
|
||
card.Controls.Add(btnSelectFile);
|
||
card.Controls.Add(hint);
|
||
}
|
||
private void TrySelectCertificateFile()
|
||
{
|
||
using OpenFileDialog dialog = new OpenFileDialog
|
||
{
|
||
Title = "Zertifikatsdatei auswählen",
|
||
Filter =
|
||
"Unterstützte Zertifikatsdateien (*.cer;*.crt;*.pem;*.pfx)|*.cer;*.crt;*.pem;*.pfx|" +
|
||
"CER-Zertifikate (*.cer)|*.cer|" +
|
||
"CRT-Zertifikate (*.crt)|*.crt|" +
|
||
"PEM-Zertifikate (*.pem)|*.pem|" +
|
||
"PFX-Zertifikate (*.pfx)|*.pfx",
|
||
FilterIndex = 1,
|
||
Multiselect = false,
|
||
CheckFileExists = true,
|
||
CheckPathExists = true,
|
||
RestoreDirectory = true,
|
||
AddExtension = true,
|
||
DereferenceLinks = true
|
||
};
|
||
|
||
// Bei Abbruch bleibt der bisherige Zustand unverändert.
|
||
if (dialog.ShowDialog(this) != DialogResult.OK)
|
||
{
|
||
return;
|
||
}
|
||
|
||
string selectedPath = dialog.FileName;
|
||
|
||
if (!IsSupportedCertificateFile(selectedPath))
|
||
{
|
||
MessageBox.Show(
|
||
this,
|
||
"Bitte wähle eine Zertifikatsdatei in einem der folgenden Formate aus:\n" +
|
||
".cer, .crt, .pem oder .pfx",
|
||
"Nicht unterstütztes Dateiformat",
|
||
MessageBoxButtons.OK,
|
||
MessageBoxIcon.Warning);
|
||
return;
|
||
}
|
||
|
||
CertificateInfo? certificateInfo = TryReadSelectedCertificate(selectedPath);
|
||
if (certificateInfo == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
txtCertificatePath.Text = selectedPath;
|
||
_loadedCertificateInfo = certificateInfo;
|
||
SetStatus($"Zertifikat ausgewählt: {Path.GetFileName(selectedPath)}", isError: false);
|
||
DisplayCertificateInfo(certificateInfo);
|
||
UpdateToNextStep(2);
|
||
SetFileButtonState(fileLoaded: true);
|
||
RefreshActionButtonStates();
|
||
}
|
||
|
||
private CertificateInfo? TryReadSelectedCertificate(string selectedPath)
|
||
{
|
||
bool isPfx = Path.GetExtension(selectedPath)
|
||
.Equals(".pfx", StringComparison.OrdinalIgnoreCase);
|
||
|
||
try
|
||
{
|
||
// Zuerst ohne Passwort versuchen
|
||
return ReadCertificate(selectedPath, null);
|
||
}
|
||
catch (CryptographicException) when (isPfx)
|
||
{
|
||
// Passwort nur abfragen, wenn das Laden ohne Passwort fehlgeschlagen ist
|
||
string? pfxPassword = PromptForPfxPassword();
|
||
if (pfxPassword == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
try
|
||
{
|
||
return ReadCertificate(selectedPath, pfxPassword);
|
||
}
|
||
catch (CryptographicException)
|
||
{
|
||
MessageBox.Show(
|
||
"Das angegebene Passwort ist falsch oder die Datei ist beschädigt.",
|
||
"Fehler beim Lesen des Zertifikats",
|
||
MessageBoxButtons.OK,
|
||
MessageBoxIcon.Error);
|
||
return null;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show(
|
||
this,
|
||
$"Das Zertifikat konnte nicht gelesen werden.\n\n{ex.Message}",
|
||
"Fehler beim Lesen des Zertifikats",
|
||
MessageBoxButtons.OK,
|
||
MessageBoxIcon.Error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
private void ClearSelectedCertificate()
|
||
{
|
||
txtCertificatePath.Text = string.Empty;
|
||
_loadedCertificateInfo = null;
|
||
SetStatus("Keine Datei ausgewählt", isError: false);
|
||
|
||
lblCertificateSubject.Text = "-";
|
||
lblCertificateIssuer.Text = "-";
|
||
lblCertificateExpiry.Text = "-";
|
||
lblCertificateFingerprint.Text = "-";
|
||
SetValidityBadge(
|
||
"○ NICHT GELADEN",
|
||
MutedTextColor,
|
||
Color.FromArgb(39, 52, 73));
|
||
|
||
UpdateToNextStep(1);
|
||
SetFileButtonState(fileLoaded: false);
|
||
RefreshActionButtonStates();
|
||
}
|
||
|
||
private void SetFileButtonState(bool fileLoaded)
|
||
{
|
||
isCertificateFileLoaded = fileLoaded;
|
||
|
||
if (fileLoaded)
|
||
{
|
||
btnSelectFile.Text = "Datei auswerfen";
|
||
btnSelectFile.BackColor = Color.FromArgb(72, 31, 38);
|
||
btnSelectFile.ForeColor = Color.FromArgb(255, 210, 210);
|
||
btnSelectFile.FlatAppearance.BorderColor = RedColor;
|
||
btnSelectFile.FlatAppearance.MouseOverBackColor = Color.FromArgb(96, 42, 50);
|
||
return;
|
||
}
|
||
|
||
btnSelectFile.Text = "Datei auswählen";
|
||
btnSelectFile.BackColor = Color.FromArgb(39, 52, 73);
|
||
btnSelectFile.ForeColor = TextColor;
|
||
btnSelectFile.FlatAppearance.BorderColor = BorderColor;
|
||
btnSelectFile.FlatAppearance.MouseOverBackColor = Color.FromArgb(54, 69, 94);
|
||
}
|
||
|
||
private void SetValidityBadge(string text, Color foreColor, Color backColor)
|
||
{
|
||
lblCertificateValidity.Text = text;
|
||
lblCertificateValidity.ForeColor = foreColor;
|
||
lblCertificateValidity.BackColor = backColor;
|
||
RepositionValidityBadge();
|
||
}
|
||
|
||
private void RepositionValidityBadge()
|
||
{
|
||
if (lblCertificateValidity?.Parent == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
lblCertificateValidity.Left =
|
||
lblCertificateValidity.Parent.Width - lblCertificateValidity.Width - 22;
|
||
}
|
||
|
||
private static string? PromptForPfxPassword()
|
||
{
|
||
using Form dialog = new Form
|
||
{
|
||
Text = "PFX-Passwort eingeben",
|
||
Size = new Size(360, 150),
|
||
StartPosition = FormStartPosition.CenterParent,
|
||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||
MinimizeBox = false,
|
||
MaximizeBox = false
|
||
};
|
||
|
||
Label label = new Label
|
||
{
|
||
Text = "Passwort für die PFX-Datei:",
|
||
Location = new Point(12, 15),
|
||
AutoSize = true
|
||
};
|
||
|
||
TextBox txtPwd = new TextBox
|
||
{
|
||
Location = new Point(12, 38),
|
||
Width = 320,
|
||
UseSystemPasswordChar = true
|
||
};
|
||
|
||
Button btnOk = new Button
|
||
{
|
||
Text = "OK",
|
||
DialogResult = DialogResult.OK,
|
||
Location = new Point(176, 72),
|
||
Width = 75
|
||
};
|
||
|
||
Button btnCancel = new Button
|
||
{
|
||
Text = "Abbrechen",
|
||
DialogResult = DialogResult.Cancel,
|
||
Location = new Point(257, 72),
|
||
Width = 75
|
||
};
|
||
|
||
dialog.Controls.AddRange([label, txtPwd, btnOk, btnCancel]);
|
||
dialog.AcceptButton = btnOk;
|
||
dialog.CancelButton = btnCancel;
|
||
|
||
return dialog.ShowDialog() == DialogResult.OK ? txtPwd.Text : null;
|
||
}
|
||
|
||
private static CertificateInfo ReadCertificate(string certificatePath, string? pfxPassword = null)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(certificatePath))
|
||
{
|
||
throw new ArgumentException(
|
||
"Es wurde kein Zertifikatspfad angegeben.",
|
||
nameof(certificatePath));
|
||
}
|
||
|
||
if (!File.Exists(certificatePath))
|
||
{
|
||
throw new FileNotFoundException(
|
||
"Die ausgewählte Zertifikatsdatei wurde nicht gefunden.",
|
||
certificatePath);
|
||
}
|
||
|
||
string extension = Path.GetExtension(certificatePath);
|
||
|
||
using X509Certificate2 certificate =
|
||
extension.Equals(".pfx", StringComparison.OrdinalIgnoreCase)
|
||
? new X509Certificate2(
|
||
certificatePath,
|
||
pfxPassword,
|
||
X509KeyStorageFlags.EphemeralKeySet)
|
||
: new X509Certificate2(certificatePath);
|
||
|
||
string subject = certificate.GetNameInfo(
|
||
X509NameType.SimpleName,
|
||
forIssuer: false);
|
||
|
||
string issuer = certificate.GetNameInfo(
|
||
X509NameType.SimpleName,
|
||
forIssuer: true);
|
||
|
||
if (string.IsNullOrWhiteSpace(subject))
|
||
{
|
||
subject = certificate.Subject;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(issuer))
|
||
{
|
||
issuer = certificate.Issuer;
|
||
}
|
||
|
||
string fingerprint = certificate.GetCertHashString(
|
||
HashAlgorithmName.SHA256);
|
||
|
||
fingerprint = FormatFingerprint(fingerprint);
|
||
|
||
DateTimeOffset validFrom =
|
||
new DateTimeOffset(certificate.NotBefore);
|
||
|
||
DateTimeOffset validUntil =
|
||
new DateTimeOffset(certificate.NotAfter);
|
||
|
||
DateTimeOffset now = DateTimeOffset.Now;
|
||
|
||
bool isCurrentlyValid =
|
||
now >= validFrom &&
|
||
now <= validUntil;
|
||
|
||
return new CertificateInfo
|
||
{
|
||
Subject = subject,
|
||
Issuer = issuer,
|
||
ValidFrom = validFrom,
|
||
ValidUntil = validUntil,
|
||
FingerprintSha256 = fingerprint,
|
||
IsCurrentlyValid = isCurrentlyValid
|
||
};
|
||
}
|
||
|
||
private void DisplayCertificateInfo(CertificateInfo certificateInfo)
|
||
{
|
||
lblCertificateSubject.Text = certificateInfo.Subject;
|
||
lblCertificateIssuer.Text = certificateInfo.Issuer;
|
||
lblCertificateExpiry.Text =
|
||
certificateInfo.ValidUntil.ToLocalTime().ToString("dd.MM.yyyy HH:mm");
|
||
lblCertificateFingerprint.Text = certificateInfo.FingerprintSha256;
|
||
|
||
if (certificateInfo.IsCurrentlyValid)
|
||
{
|
||
SetValidityBadge("● GÜLTIG", GreenColor, Color.FromArgb(20, 62, 46));
|
||
return;
|
||
}
|
||
|
||
SetValidityBadge(
|
||
"● ABGELAUFEN / NICHT GÜLTIG",
|
||
RedColor,
|
||
Color.FromArgb(72, 31, 38));
|
||
}
|
||
private static string FormatFingerprint(string fingerprint)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(fingerprint))
|
||
{
|
||
return string.Empty;
|
||
}
|
||
|
||
return string.Join(
|
||
":",
|
||
Enumerable.Range(0, fingerprint.Length / 2)
|
||
.Select(index => fingerprint.Substring(index * 2, 2)));
|
||
}
|
||
private static bool IsSupportedCertificateFile(string filePath)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(filePath))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!File.Exists(filePath))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
string extension = Path.GetExtension(filePath);
|
||
|
||
string[] supportedExtensions =
|
||
{
|
||
".cer",
|
||
".crt",
|
||
".pem",
|
||
".pfx"
|
||
};
|
||
|
||
return supportedExtensions.Contains(
|
||
extension,
|
||
StringComparer.OrdinalIgnoreCase);
|
||
}
|
||
private void BuildDetailsCard(Panel card)
|
||
{
|
||
Label step = CreateSmallTitle("ERKANNTE INFORMATIONEN");
|
||
step.Location = new Point(22, 18);
|
||
|
||
Label title = CreateCardTitle("Zertifikatsdetails");
|
||
title.Location = new Point(22, 40);
|
||
|
||
lblCertificateValidity = new Label
|
||
{
|
||
Text = "○ NICHT GELADEN",
|
||
ForeColor = MutedTextColor,
|
||
BackColor = Color.FromArgb(39, 52, 73),
|
||
Font = new Font("Segoe UI", 8, FontStyle.Bold),
|
||
AutoSize = true,
|
||
Padding = new Padding(8, 5, 8, 5),
|
||
Anchor = AnchorStyles.Top | AnchorStyles.Right
|
||
};
|
||
|
||
lblCertificateValidity.Location = new Point(300, 40);
|
||
|
||
card.Resize += (_, _) => RepositionValidityBadge();
|
||
|
||
Label subjectLabel = CreateMutedLabel("SUBJECT / CN");
|
||
subjectLabel.Location = new Point(22, 84);
|
||
|
||
lblCertificateSubject = CreateValueLabel("-");
|
||
lblCertificateSubject.Location = new Point(22, 102);
|
||
lblCertificateSubject.AutoEllipsis = true;
|
||
lblCertificateSubject.MaximumSize = new Size(210, 0);
|
||
|
||
Label issuerLabel = CreateMutedLabel("AUSSTELLER");
|
||
issuerLabel.Location = new Point(22, 133);
|
||
|
||
lblCertificateIssuer = CreateValueLabel("-");
|
||
lblCertificateIssuer.Location = new Point(22, 151);
|
||
lblCertificateIssuer.AutoEllipsis = true;
|
||
lblCertificateIssuer.MaximumSize = new Size(210, 0);
|
||
|
||
Label expiryLabel = CreateMutedLabel("GÜLTIG BIS");
|
||
expiryLabel.Location = new Point(22, 182);
|
||
|
||
lblCertificateExpiry = CreateValueLabel("-");
|
||
lblCertificateExpiry.Location = new Point(22, 200);
|
||
|
||
Label fingerprintLabel = CreateMutedLabel("SHA-256 FINGERPRINT");
|
||
fingerprintLabel.Location = new Point(245, 84);
|
||
|
||
lblCertificateFingerprint = CreateValueLabel("-");
|
||
lblCertificateFingerprint.Location = new Point(245, 102);
|
||
lblCertificateFingerprint.AutoSize = false;
|
||
lblCertificateFingerprint.Size = new Size(310, 48);
|
||
|
||
card.Controls.Add(step);
|
||
card.Controls.Add(title);
|
||
card.Controls.Add(lblCertificateValidity);
|
||
|
||
card.Controls.Add(subjectLabel);
|
||
card.Controls.Add(lblCertificateSubject);
|
||
|
||
card.Controls.Add(issuerLabel);
|
||
card.Controls.Add(lblCertificateIssuer);
|
||
|
||
card.Controls.Add(expiryLabel);
|
||
card.Controls.Add(lblCertificateExpiry);
|
||
|
||
card.Controls.Add(fingerprintLabel);
|
||
card.Controls.Add(lblCertificateFingerprint);
|
||
}
|
||
|
||
private Panel BuildTargetsCard()
|
||
{
|
||
Panel card = CreateCard();
|
||
|
||
Label step = CreateSmallTitle("SCHRITT 2");
|
||
step.Location = new Point(22, 18);
|
||
|
||
Label title = CreateCardTitle("Bereitstellungsziele");
|
||
title.Location = new Point(22, 40);
|
||
|
||
Label description = CreateMutedLabel(
|
||
"Wähle aus, auf welche Systeme das Zertifikat verteilt werden soll.");
|
||
description.Location = new Point(22, 72);
|
||
|
||
dgvTargets = new DataGridView
|
||
{
|
||
Location = new Point(22, 148),
|
||
Size = new Size(1000, 205),
|
||
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
|
||
BackgroundColor = CardColor,
|
||
BorderStyle = BorderStyle.None,
|
||
EnableHeadersVisualStyles = false,
|
||
AllowUserToAddRows = false,
|
||
AllowUserToDeleteRows = false,
|
||
AllowUserToResizeRows = false,
|
||
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
|
||
RowHeadersVisible = false,
|
||
SelectionMode = DataGridViewSelectionMode.FullRowSelect,
|
||
MultiSelect = false,
|
||
GridColor = BorderColor
|
||
};
|
||
|
||
dgvTargets.ColumnHeadersDefaultCellStyle = new DataGridViewCellStyle
|
||
{
|
||
BackColor = Color.FromArgb(24, 34, 52),
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 8.5f, FontStyle.Bold),
|
||
Alignment = DataGridViewContentAlignment.MiddleLeft
|
||
};
|
||
|
||
dgvTargets.DefaultCellStyle = new DataGridViewCellStyle
|
||
{
|
||
BackColor = CardColor,
|
||
ForeColor = TextColor,
|
||
SelectionBackColor = Color.FromArgb(40, 57, 82),
|
||
SelectionForeColor = TextColor,
|
||
Font = new Font("Segoe UI", 9),
|
||
Padding = new Padding(4, 0, 4, 0)
|
||
};
|
||
|
||
dgvTargets.AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle
|
||
{
|
||
BackColor = Color.FromArgb(28, 39, 57),
|
||
ForeColor = TextColor
|
||
};
|
||
|
||
dgvTargets.ColumnHeadersHeight = 34;
|
||
dgvTargets.RowTemplate.Height = 32;
|
||
|
||
dgvTargets.Columns.Add(new DataGridViewCheckBoxColumn
|
||
{
|
||
Name = "Selected",
|
||
HeaderText = "",
|
||
FillWeight = 25
|
||
});
|
||
|
||
dgvTargets.Columns.Add("Target", "Ziel");
|
||
dgvTargets.Columns.Add("Environment", "Umgebung");
|
||
dgvTargets.Columns.Add("Container", "Container / Service");
|
||
dgvTargets.Columns.Add("Tls", "TLS-Prüfung");
|
||
dgvTargets.Columns.Add("Status", "Status");
|
||
|
||
dgvTargets.CurrentCellDirtyStateChanged += (_, _) =>
|
||
{
|
||
if (dgvTargets.IsCurrentCellDirty
|
||
&& dgvTargets.CurrentCell is DataGridViewCheckBoxCell)
|
||
{
|
||
dgvTargets.CommitEdit(DataGridViewDataErrorContexts.Commit);
|
||
}
|
||
};
|
||
|
||
dgvTargets.CellValueChanged += (_, e) =>
|
||
{
|
||
if (e.ColumnIndex >= 0
|
||
&& dgvTargets.Columns[e.ColumnIndex].Name == "Selected")
|
||
{
|
||
RefreshActionButtonStates();
|
||
}
|
||
};
|
||
|
||
card.Resize += (_, _) =>
|
||
{
|
||
dgvTargets.Width = card.Width - 44;
|
||
};
|
||
|
||
card.Controls.Add(step);
|
||
card.Controls.Add(title);
|
||
card.Controls.Add(description);
|
||
card.Controls.Add(dgvTargets);
|
||
|
||
// ---- Sonic Management Console Discovery ----
|
||
BuildSonicDiscoveryRow(card);
|
||
|
||
return card;
|
||
}
|
||
|
||
private Panel BuildActionPanel()
|
||
{
|
||
Panel panel = new Panel
|
||
{
|
||
BackColor = BackgroundColor
|
||
};
|
||
|
||
btnCancelRun = CreateSecondaryButton("Abbrechen");
|
||
btnCancelRun.Size = new Size(120, 42);
|
||
btnCancelRun.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||
btnCancelRun.Enabled = false;
|
||
btnCancelRun.Click += (_, _) =>
|
||
{
|
||
_runCts?.Cancel();
|
||
SetStatus("Abbruch angefordert…", isError: false);
|
||
};
|
||
|
||
btnValidate = CreateSecondaryButton("Prüfung durchführen");
|
||
btnValidate.Size = new Size(175, 42);
|
||
btnValidate.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||
btnValidate.Click += async (_, _) => await RunValidationAsync();
|
||
|
||
// Primäraktion: Neustart (Deploy vorerst deaktiviert – Design/Layout bleibt)
|
||
btnRestartOnly = CreatePrimaryButton("ESB neu starten");
|
||
btnRestartOnly.Size = new Size(170, 42);
|
||
btnRestartOnly.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||
btnRestartOnly.Click += async (_, _) => await RunRestartOnlyAsync();
|
||
|
||
btnDeploy = CreatePrimaryButton("Deployment starten");
|
||
btnDeploy.Size = new Size(170, 42);
|
||
btnDeploy.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||
btnDeploy.Visible = true;
|
||
btnDeploy.Click += async (_, _) => await RunDeploymentAsync();
|
||
|
||
panel.Controls.Add(btnCancelRun);
|
||
panel.Controls.Add(btnValidate);
|
||
panel.Controls.Add(btnRestartOnly);
|
||
panel.Controls.Add(btnDeploy);
|
||
|
||
void PositionActionButtons()
|
||
{
|
||
const int spacing = 12;
|
||
const int rightMargin = 0;
|
||
|
||
// Rechts nach links anordnen:
|
||
// Deployment | Neustart | Prüfung | Abbrechen
|
||
btnDeploy.Left = panel.ClientSize.Width - btnDeploy.Width - rightMargin;
|
||
btnRestartOnly.Left = btnDeploy.Left - btnRestartOnly.Width - spacing;
|
||
btnValidate.Left = btnRestartOnly.Left - btnValidate.Width - spacing;
|
||
btnCancelRun.Left = btnValidate.Left - btnCancelRun.Width - spacing;
|
||
}
|
||
|
||
panel.Resize += (_, _) => PositionActionButtons();
|
||
PositionActionButtons();
|
||
|
||
RefreshActionButtonStates();
|
||
return panel;
|
||
}
|
||
|
||
private Panel BuildStatusBar()
|
||
{
|
||
Panel panel = new Panel
|
||
{
|
||
Height = 28,
|
||
BackColor = BackgroundColor
|
||
};
|
||
|
||
lblStatusDot = new Label
|
||
{
|
||
Text = "●",
|
||
ForeColor = GreenColor,
|
||
Font = new Font("Segoe UI", 10),
|
||
AutoSize = true,
|
||
Location = new Point(0, 4)
|
||
};
|
||
|
||
lblStatus = new Label
|
||
{
|
||
Text = "Warte auf Zertifikatsauswahl",
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 9),
|
||
AutoSize = true,
|
||
Location = new Point(18, 5)
|
||
};
|
||
|
||
panel.Controls.Add(lblStatusDot);
|
||
panel.Controls.Add(lblStatus);
|
||
|
||
return panel;
|
||
}
|
||
|
||
private async Task LoadTargetsAsync()
|
||
{
|
||
try
|
||
{
|
||
SetStatus(
|
||
"Bereitstellungsziele werden aus SQL geladen ...",
|
||
isError: false);
|
||
|
||
_loadedTargets =
|
||
await _deploymentTargetRepository.GetActiveAsync();
|
||
|
||
BindTargetsToGrid(_loadedTargets);
|
||
|
||
if (_loadedTargets.Count == 0)
|
||
{
|
||
SetStatus(
|
||
"In SQL sind keine aktiven Bereitstellungsziele konfiguriert.",
|
||
isError: true);
|
||
|
||
return;
|
||
}
|
||
|
||
SetStatus(
|
||
$"{_loadedTargets.Count} Bereitstellungsziel(e) aus SQL geladen.",
|
||
isError: false);
|
||
}
|
||
catch
|
||
{
|
||
_loadedTargets = [];
|
||
dgvTargets.Rows.Clear();
|
||
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
RefreshActionButtonStates();
|
||
}
|
||
}
|
||
|
||
private void BindTargetsToGrid(IReadOnlyList<DeploymentTarget> targets)
|
||
{
|
||
dgvTargets.Rows.Clear();
|
||
|
||
foreach (DeploymentTarget target in targets)
|
||
{
|
||
int tlsPort = target.TlsPort is > 0 ? target.TlsPort.Value : 443;
|
||
string tls = string.IsNullOrWhiteSpace(target.TlsHost)
|
||
? "—"
|
||
: $"{target.TlsHost}:{tlsPort}";
|
||
|
||
int rowIndex = dgvTargets.Rows.Add(
|
||
false,
|
||
target.Name,
|
||
target.Environment,
|
||
target.ContainerName,
|
||
tls,
|
||
"Bereit");
|
||
|
||
dgvTargets.Rows[rowIndex].Tag = target;
|
||
}
|
||
}
|
||
|
||
private List<DeploymentTarget> GetSelectedTargets()
|
||
{
|
||
List<DeploymentTarget> selected = [];
|
||
|
||
foreach (DataGridViewRow row in dgvTargets.Rows)
|
||
{
|
||
if (row.Cells["Selected"].Value is true && row.Tag is DeploymentTarget target)
|
||
{
|
||
selected.Add(target);
|
||
}
|
||
}
|
||
|
||
return selected;
|
||
}
|
||
|
||
private void SetTargetRowStatus(int targetId, string status)
|
||
{
|
||
foreach (DataGridViewRow row in dgvTargets.Rows)
|
||
{
|
||
if (row.Tag is DeploymentTarget target && target.Id == targetId)
|
||
{
|
||
row.Cells["Status"].Value = status;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void RefreshActionButtonStates()
|
||
{
|
||
if (btnValidate is null || btnDeploy is null || btnCancelRun is null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
bool idle = !_isOperationRunning;
|
||
bool hasCertificate = isCertificateFileLoaded && _loadedCertificateInfo is not null;
|
||
bool hasTargets = _loadedTargets.Count > 0;
|
||
bool hasSelection = dgvTargets is not null && GetSelectedTargets().Count > 0;
|
||
|
||
if (btnSelectFile is not null)
|
||
{
|
||
btnSelectFile.Enabled = idle;
|
||
}
|
||
|
||
btnCancelRun.Enabled = _isOperationRunning;
|
||
// Prüfung nur für Neustart-Voraussetzungen (Zertifikat optional)
|
||
btnValidate.Enabled = idle && hasTargets;
|
||
btnDeploy.Visible = true;
|
||
btnDeploy.Enabled = idle && hasCertificate && hasSelection;
|
||
if (btnRestartOnly is not null)
|
||
{
|
||
btnRestartOnly.Enabled = idle && hasSelection;
|
||
}
|
||
|
||
if (dgvTargets is not null)
|
||
{
|
||
dgvTargets.Enabled = idle;
|
||
}
|
||
|
||
}
|
||
|
||
private void SetStatus(string message, bool isError)
|
||
{
|
||
if (lblStatus is null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
lblStatus.Text = message;
|
||
lblStatus.ForeColor = isError ? RedColor : MutedTextColor;
|
||
if (lblStatusDot is not null)
|
||
{
|
||
lblStatusDot.ForeColor = isError ? RedColor : (_isOperationRunning ? GoldColor : GreenColor);
|
||
}
|
||
}
|
||
|
||
private void SetOperationRunning(bool running)
|
||
{
|
||
_isOperationRunning = running;
|
||
RefreshActionButtonStates();
|
||
}
|
||
|
||
private void ShowPreflightIssues(PreflightValidationResult result, string statusMessage, string caption)
|
||
{
|
||
string details = string.Join(Environment.NewLine, result.Issues.Select(i => "• " + i.Message));
|
||
SetStatus(statusMessage, isError: true);
|
||
MessageBox.Show(this, details, caption, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
}
|
||
|
||
private Task RunValidationAsync()
|
||
{
|
||
if (_orchestrator is null)
|
||
{
|
||
SetStatus(
|
||
"Die Anwendung ist noch nicht vollständig initialisiert.",
|
||
isError: true);
|
||
|
||
return Task.CompletedTask;
|
||
}
|
||
if (_isOperationRunning)
|
||
{
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
List<DeploymentTarget> selected = GetSelectedTargets();
|
||
PreflightValidationResult result = _orchestrator.ValidateRestartOnly(selected);
|
||
|
||
HashSet<int> failedTargetIds = result.Issues
|
||
.Where(i => i.TargetId is not null)
|
||
.Select(i => i.TargetId!.Value)
|
||
.ToHashSet();
|
||
|
||
foreach (DataGridViewRow row in dgvTargets.Rows)
|
||
{
|
||
if (row.Tag is not DeploymentTarget target)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (failedTargetIds.Contains(target.Id))
|
||
{
|
||
SetTargetRowStatus(target.Id, "Prüfung fehlgeschlagen");
|
||
}
|
||
else if (row.Cells["Selected"].Value is true)
|
||
{
|
||
SetTargetRowStatus(target.Id, "Bereit");
|
||
}
|
||
}
|
||
|
||
if (!result.IsValid)
|
||
{
|
||
ShowPreflightIssues(result, "Vorabprüfung fehlgeschlagen.", "Vorabprüfung");
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
UpdateToNextStep(3);
|
||
SetStatus($"Vorabprüfung ok – {selected.Count} Ziel(e) bereit.", isError: false);
|
||
MessageBox.Show(
|
||
this,
|
||
$"Alle Voraussetzungen sind erfüllt.\nAusgewählte Ziele: {selected.Count}",
|
||
"Vorabprüfung",
|
||
MessageBoxButtons.OK,
|
||
MessageBoxIcon.Information);
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
private async Task RunRestartOnlyAsync()
|
||
{
|
||
if (_orchestrator is null)
|
||
{
|
||
SetStatus(
|
||
"Die Anwendung ist noch nicht vollständig initialisiert.",
|
||
isError: true);
|
||
|
||
return;
|
||
}
|
||
if (_isOperationRunning)
|
||
{
|
||
return;
|
||
}
|
||
|
||
List<DeploymentTarget> selected = GetSelectedTargets();
|
||
PreflightValidationResult preflight = _orchestrator.ValidateRestartOnly(selected);
|
||
|
||
if (!preflight.IsValid)
|
||
{
|
||
ShowPreflightIssues(
|
||
preflight,
|
||
"Neustart blockiert – Vorabprüfung fehlgeschlagen.",
|
||
"ESB neu starten");
|
||
return;
|
||
}
|
||
|
||
string targetNames = string.Join(", ", selected.Select(t =>
|
||
string.IsNullOrWhiteSpace(t.ContainerName) ? t.Name : t.ContainerName));
|
||
|
||
DialogResult confirm = MessageBox.Show(
|
||
this,
|
||
$"Container wirklich neu starten?\n\n" +
|
||
$"Ziele: {targetNames}\n\n",
|
||
"ESB neu starten",
|
||
MessageBoxButtons.YesNo,
|
||
MessageBoxIcon.Warning);
|
||
|
||
if (confirm != DialogResult.Yes)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_runCts?.Dispose();
|
||
_runCts = new CancellationTokenSource();
|
||
SetOperationRunning(true);
|
||
UpdateToNextStep(3);
|
||
SetStatus($"ESB-Neustart läuft für {selected.Count} Ziel(e)…", isError: false);
|
||
|
||
Progress<TargetProgressUpdate> progress = new(update =>
|
||
{
|
||
SetTargetRowStatus(update.TargetId, update.StatusText);
|
||
SetStatus(update.StatusText, isError: update.SuccessHint == false);
|
||
});
|
||
|
||
try
|
||
{
|
||
DeploymentRunResult runResult = await _orchestrator.RestartOnlyAsync(
|
||
selected,
|
||
progress,
|
||
_runCts.Token);
|
||
|
||
foreach (TargetStepResult targetResult in runResult.TargetResults)
|
||
{
|
||
SetTargetRowStatus(targetResult.TargetId, targetResult.StatusText);
|
||
}
|
||
|
||
SetStatus(
|
||
runResult.OverallSuccess
|
||
? $"Neustart verifiziert ({runResult.TargetResults.Count} Ziel(e))."
|
||
: "Neustart fehlgeschlagen oder nicht verifiziert.",
|
||
isError: !runResult.OverallSuccess);
|
||
|
||
if (runResult.OverallSuccess)
|
||
{
|
||
UpdateToNextStep(4);
|
||
}
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
SetStatus("Neustart abgebrochen.", isError: true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetStatus($"Neustart fehlgeschlagen: {ex.Message}", isError: true);
|
||
MessageBox.Show(this, ex.Message, "ESB neu starten", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
finally
|
||
{
|
||
SetOperationRunning(false);
|
||
_runCts?.Dispose();
|
||
_runCts = null;
|
||
}
|
||
}
|
||
|
||
private async Task RunDeploymentAsync()
|
||
{
|
||
if (_orchestrator is null)
|
||
{
|
||
SetStatus(
|
||
"Die Anwendung ist noch nicht vollständig initialisiert.",
|
||
isError: true);
|
||
|
||
return;
|
||
}
|
||
if (_isOperationRunning)
|
||
{
|
||
return;
|
||
}
|
||
|
||
List<DeploymentTarget> selectedTargets = GetSelectedTargets();
|
||
|
||
if (_loadedCertificateInfo is null ||
|
||
string.IsNullOrWhiteSpace(txtCertificatePath.Text) ||
|
||
!File.Exists(txtCertificatePath.Text))
|
||
{
|
||
SetStatus(
|
||
"Bitte zuerst eine gültige Zertifikatsdatei auswählen.",
|
||
isError: true);
|
||
|
||
return;
|
||
}
|
||
|
||
if (selectedTargets.Count == 0)
|
||
{
|
||
SetStatus(
|
||
"Bitte mindestens ein Ziel auswählen.",
|
||
isError: true);
|
||
|
||
return;
|
||
}
|
||
|
||
DialogResult confirm = MessageBox.Show(
|
||
this,
|
||
$"Zertifikat wirklich auf {selectedTargets.Count} Ziel(e) kopieren?\n\n" +
|
||
$"Datei: {Path.GetFileName(txtCertificatePath.Text)}",
|
||
"Deployment starten",
|
||
MessageBoxButtons.YesNo,
|
||
MessageBoxIcon.Warning);
|
||
|
||
if (confirm != DialogResult.Yes)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_runCts?.Dispose();
|
||
_runCts = new CancellationTokenSource();
|
||
|
||
SetOperationRunning(true);
|
||
UpdateToNextStep(3);
|
||
|
||
SetStatus(
|
||
$"Kopiere Zertifikat auf {selectedTargets.Count} Ziel(e) ...",
|
||
isError: false);
|
||
|
||
Progress<TargetProgressUpdate> progress = new(update =>
|
||
{
|
||
SetTargetRowStatus(update.TargetId, update.StatusText);
|
||
SetStatus(update.StatusText, isError: update.SuccessHint == false);
|
||
});
|
||
|
||
try
|
||
{
|
||
DeploymentRunResult runResult = await _orchestrator.DeployAsync(
|
||
txtCertificatePath.Text,
|
||
_loadedCertificateInfo.FingerprintSha256,
|
||
selectedTargets,
|
||
progress,
|
||
_runCts.Token);
|
||
|
||
foreach (TargetStepResult targetResult in runResult.TargetResults)
|
||
{
|
||
SetTargetRowStatus(
|
||
targetResult.TargetId,
|
||
targetResult.StatusText);
|
||
}
|
||
|
||
if (runResult.OverallSuccess)
|
||
{
|
||
SetStatus(
|
||
$"Deployment erfolgreich: {runResult.TargetResults.Count} Ziel(e).",
|
||
isError: false);
|
||
|
||
UpdateToNextStep(4);
|
||
}
|
||
else
|
||
{
|
||
SetStatus(
|
||
"Deployment für mindestens ein Ziel fehlgeschlagen.",
|
||
isError: true);
|
||
}
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
SetStatus("Deployment abgebrochen.", isError: true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetStatus(
|
||
$"Deployment fehlgeschlagen: {ex.Message}",
|
||
isError: true);
|
||
|
||
MessageBox.Show(
|
||
this,
|
||
ex.Message,
|
||
"Deployment fehlgeschlagen",
|
||
MessageBoxButtons.OK,
|
||
MessageBoxIcon.Error);
|
||
}
|
||
finally
|
||
{
|
||
SetOperationRunning(false);
|
||
|
||
_runCts?.Dispose();
|
||
_runCts = null;
|
||
}
|
||
}
|
||
|
||
private Panel CreateCard()
|
||
{
|
||
return new Panel
|
||
{
|
||
BackColor = CardColor,
|
||
BorderStyle = BorderStyle.FixedSingle,
|
||
Padding = new Padding(20)
|
||
};
|
||
}
|
||
|
||
private Label CreateSmallTitle(string text)
|
||
{
|
||
return new Label
|
||
{
|
||
Text = text,
|
||
ForeColor = BlueColor,
|
||
Font = new Font("Segoe UI", 8, FontStyle.Bold),
|
||
AutoSize = true
|
||
};
|
||
}
|
||
|
||
private Label CreateCardTitle(string text)
|
||
{
|
||
return new Label
|
||
{
|
||
Text = text,
|
||
ForeColor = TextColor,
|
||
Font = new Font("Segoe UI", 14, FontStyle.Bold),
|
||
AutoSize = true
|
||
};
|
||
}
|
||
|
||
private Label CreateMutedLabel(string text)
|
||
{
|
||
return new Label
|
||
{
|
||
Text = text,
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 9),
|
||
AutoSize = true
|
||
};
|
||
}
|
||
|
||
private Label CreateValueLabel(string text)
|
||
{
|
||
return new Label
|
||
{
|
||
Text = text,
|
||
ForeColor = TextColor,
|
||
Font = new Font("Segoe UI", 9.5f, FontStyle.Regular),
|
||
AutoSize = true
|
||
};
|
||
}
|
||
|
||
private Button CreatePrimaryButton(string text)
|
||
{
|
||
Button button = new Button
|
||
{
|
||
Text = text,
|
||
BackColor = GoldColor,
|
||
ForeColor = Color.FromArgb(30, 25, 10),
|
||
FlatStyle = FlatStyle.Flat,
|
||
FlatAppearance =
|
||
{
|
||
BorderSize = 0,
|
||
MouseOverBackColor = Color.FromArgb(250, 204, 21)
|
||
},
|
||
Font = new Font("Segoe UI", 9.5f, FontStyle.Bold),
|
||
Cursor = Cursors.Hand
|
||
};
|
||
|
||
return button;
|
||
}
|
||
|
||
private Button CreateSecondaryButton(string text)
|
||
{
|
||
Button button = new Button
|
||
{
|
||
Text = text,
|
||
BackColor = Color.FromArgb(39, 52, 73),
|
||
ForeColor = TextColor,
|
||
FlatStyle = FlatStyle.Flat,
|
||
FlatAppearance =
|
||
{
|
||
BorderColor = BorderColor,
|
||
BorderSize = 1,
|
||
MouseOverBackColor = Color.FromArgb(54, 69, 94)
|
||
},
|
||
Font = new Font("Segoe UI", 9, FontStyle.Bold),
|
||
Cursor = Cursors.Hand
|
||
};
|
||
|
||
return button;
|
||
}
|
||
|
||
// ---------------------------------------------------------------
|
||
// Sonic – Status (KnownContainers aus appsettings)
|
||
// ---------------------------------------------------------------
|
||
private void BindDatabaseSonicConnections()
|
||
{
|
||
cmbSonicConnection.Items.Clear();
|
||
|
||
foreach (DatabaseSonicConnection connection
|
||
in _databaseSonicConnections)
|
||
{
|
||
cmbSonicConnection.Items.Add(connection.Name);
|
||
}
|
||
|
||
if (cmbSonicConnection.Items.Count > 0)
|
||
{
|
||
cmbSonicConnection.SelectedIndex = 0;
|
||
return;
|
||
}
|
||
|
||
RefreshSonicStatusLine();
|
||
}
|
||
private void BuildSonicDiscoveryRow(Panel card)
|
||
{
|
||
Label sonicLabel = new Label
|
||
{
|
||
Text = "Sonic Verbindung:",
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 8.5f, FontStyle.Bold),
|
||
AutoSize = true,
|
||
Location = new Point(22, 108)
|
||
};
|
||
|
||
cmbSonicConnection = new ComboBox
|
||
{
|
||
Location = new Point(160, 104),
|
||
Size = new Size(220, 26),
|
||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||
BackColor = Color.FromArgb(20, 30, 48),
|
||
ForeColor = TextColor,
|
||
FlatStyle = FlatStyle.Flat,
|
||
Font = new Font("Segoe UI", 9)
|
||
};
|
||
|
||
lblSonicStatus = new Label
|
||
{
|
||
ForeColor = MutedTextColor,
|
||
Font = new Font("Segoe UI", 8.5f),
|
||
AutoSize = true,
|
||
MaximumSize = new Size(720, 0),
|
||
Location = new Point(392, 108)
|
||
};
|
||
|
||
cmbSonicConnection.SelectedIndexChanged += (_, _) => RefreshSonicStatusLine();
|
||
|
||
lblSonicStatus.Text = "Sonic-Verbindungen werden aus SQL geladen ...";
|
||
lblSonicStatus.ForeColor = MutedTextColor;
|
||
|
||
card.Controls.Add(sonicLabel);
|
||
card.Controls.Add(cmbSonicConnection);
|
||
card.Controls.Add(lblSonicStatus);
|
||
}
|
||
|
||
private void RefreshSonicStatusLine()
|
||
{
|
||
if (_databaseSonicConnections.Count == 0)
|
||
{
|
||
lblSonicStatus.Text =
|
||
"Keine aktive Sonic-Verbindung in SQL konfiguriert.";
|
||
|
||
lblSonicStatus.ForeColor = GoldColor;
|
||
return;
|
||
}
|
||
|
||
string? selectedName =
|
||
cmbSonicConnection.SelectedItem?.ToString();
|
||
|
||
DatabaseSonicConnection? connection =
|
||
_databaseSonicConnections.FirstOrDefault(
|
||
item => string.Equals(
|
||
item.Name,
|
||
selectedName,
|
||
StringComparison.OrdinalIgnoreCase));
|
||
|
||
if (connection is null)
|
||
{
|
||
lblSonicStatus.Text =
|
||
"Die ausgewählte SQL-Verbindung wurde nicht gefunden.";
|
||
|
||
lblSonicStatus.ForeColor = RedColor;
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
string connectionUrl = connection.BuildConnectionUrl();
|
||
|
||
bool hasCredentialReference =
|
||
!string.IsNullOrWhiteSpace(connection.CredentialReference);
|
||
|
||
lblSonicStatus.Text = hasCredentialReference
|
||
? $"{connection.Name}: {connectionUrl}, Credential-Referenz vorhanden"
|
||
: $"{connection.Name}: {connectionUrl}, sichere Zugangsdaten noch nicht konfiguriert";
|
||
|
||
lblSonicStatus.ForeColor = hasCredentialReference
|
||
? GreenColor
|
||
: GoldColor;
|
||
}
|
||
catch (InvalidOperationException ex)
|
||
{
|
||
lblSonicStatus.Text = ex.Message;
|
||
lblSonicStatus.ForeColor = RedColor;
|
||
}
|
||
}
|
||
|
||
private void Form1_Load(object sender, EventArgs e)
|
||
{
|
||
|
||
}
|
||
}
|
||
}
|