big changes

This commit is contained in:
Mike Gisler
2026-07-28 08:03:12 +02:00
parent c73f53d740
commit 4d19873776
179 changed files with 14953 additions and 494 deletions
+374 -71
View File
@@ -1,16 +1,15 @@
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
{
// ---------------------------------------------------------
// ZIEHL-ABEGG-inspirierte Dark-Mode-Farbpalette
// ---------------------------------------------------------
// Große Hintergrundflächen
private readonly Color BackgroundColor = Color.FromArgb(10, 18, 34); // #0A1222
@@ -32,8 +31,17 @@ namespace ZA.CoreService.ESBCertificateManager
private readonly Color RedColor = Color.FromArgb(239, 106, 106); // #EF6A6A
private readonly AppSettings _settings;
private readonly DeploymentOrchestrator _orchestrator;
private readonly SonicContainerDiscovery _sonicDiscovery;
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!;
@@ -64,10 +72,6 @@ namespace ZA.CoreService.ESBCertificateManager
private Button btnSelectFile = null!;
private bool isCertificateFileLoaded;
private CertificateInfo? _loadedCertificateInfo;
private IReadOnlyList<DeploymentTarget> _loadedTargets = [];
private bool _isOperationRunning;
private CancellationTokenSource? _runCts;
private int nvbarlaststate = 1;
public Form1()
@@ -75,11 +79,19 @@ namespace ZA.CoreService.ESBCertificateManager
InitializeComponent();
_settings = AppSettingsLoader.Load();
_orchestrator = new DeploymentOrchestrator(_settings);
_sonicDiscovery = new SonicContainerDiscovery(_settings.SonicConnections);
_sonicConnectionRepository = new SonicConnectionRepository(
_settings.Database.ConnectionString);
_deploymentTargetRepository = new DeploymentTargetRepository(
_settings.Database.ConnectionString);
BuildDesign();
Shown += async (_, _) => await LoadTargetsAsync();
Shown += async (_, _) => await InitializeApplicationAsync();
}
private Button CreateWindowButton(string text)
{
@@ -123,7 +135,115 @@ namespace ZA.CoreService.ESBCertificateManager
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)
@@ -245,7 +365,7 @@ namespace ZA.CoreService.ESBCertificateManager
Dock = DockStyle.Right,
Width = 1,
BackColor = BorderColor
};
};
sidebar.Controls.Add(sidebarBorder);
@@ -477,14 +597,14 @@ namespace ZA.CoreService.ESBCertificateManager
if (foundCircles.Length == 0) return;
Control[] foundTitels = navProgressPanel.Controls.Find($"stepTitle{state}", true);
if (foundTitels.Length == 0) return;
Label titel = (Label)foundTitels[0];
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)
if (nvbarlaststate > state)
{
Control[] foundlastCircles = navProgressPanel.Controls.Find($"stepCircle{nvbarlaststate}", true);
if (foundlastCircles.Length == 0) return;
@@ -1239,20 +1359,29 @@ namespace ZA.CoreService.ESBCertificateManager
btnDeploy = CreatePrimaryButton("Deployment starten");
btnDeploy.Size = new Size(170, 42);
btnDeploy.Anchor = AnchorStyles.Top | AnchorStyles.Right;
btnDeploy.Visible = false;
btnDeploy.Enabled = false;
btnDeploy.Visible = true;
btnDeploy.Click += async (_, _) => await RunDeploymentAsync();
panel.Controls.Add(btnCancelRun);
panel.Controls.Add(btnValidate);
panel.Controls.Add(btnRestartOnly);
panel.Controls.Add(btnDeploy);
panel.Resize += (_, _) =>
void PositionActionButtons()
{
btnRestartOnly.Left = panel.Width - btnRestartOnly.Width;
btnValidate.Left = btnRestartOnly.Left - btnValidate.Width - 12;
btnCancelRun.Left = btnValidate.Left - btnCancelRun.Width - 12;
};
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;
@@ -1290,37 +1419,43 @@ namespace ZA.CoreService.ESBCertificateManager
return panel;
}
private Task LoadTargetsAsync()
private async Task LoadTargetsAsync()
{
try
{
SetStatus("Lade Container aus appsettings (KnownContainers)…", isError: false);
_loadedTargets = _sonicDiscovery.BuildTargetsFromConfig();
SetStatus(
"Bereitstellungsziele werden aus SQL geladen ...",
isError: false);
_loadedTargets =
await _deploymentTargetRepository.GetActiveAsync();
BindTargetsToGrid(_loadedTargets);
if (_loadedTargets.Count == 0)
{
SetStatus(
"Keine KnownContainers in appsettings. SonicConnections[].KnownContainers setzen.",
"In SQL sind keine aktiven Bereitstellungsziele konfiguriert.",
isError: true);
return;
}
else
{
SetStatus($"{_loadedTargets.Count} Container aus appsettings.", isError: false);
}
SetStatus(
$"{_loadedTargets.Count} Bereitstellungsziel(e) aus SQL geladen.",
isError: false);
}
catch (Exception ex)
catch
{
_loadedTargets = [];
dgvTargets.Rows.Clear();
SetStatus($"Ziele konnten nicht geladen werden: {ex.Message}", isError: true);
throw;
}
finally
{
RefreshActionButtonStates();
}
return Task.CompletedTask;
}
private void BindTargetsToGrid(IReadOnlyList<DeploymentTarget> targets)
@@ -1393,8 +1528,8 @@ namespace ZA.CoreService.ESBCertificateManager
btnCancelRun.Enabled = _isOperationRunning;
// Prüfung nur für Neustart-Voraussetzungen (Zertifikat optional)
btnValidate.Enabled = idle && hasTargets;
btnDeploy.Enabled = false;
btnDeploy.Visible = false;
btnDeploy.Visible = true;
btnDeploy.Enabled = idle && hasCertificate && hasSelection;
if (btnRestartOnly is not null)
{
btnRestartOnly.Enabled = idle && hasSelection;
@@ -1437,6 +1572,14 @@ namespace ZA.CoreService.ESBCertificateManager
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;
@@ -1486,6 +1629,14 @@ namespace ZA.CoreService.ESBCertificateManager
private async Task RunRestartOnlyAsync()
{
if (_orchestrator is null)
{
SetStatus(
"Die Anwendung ist noch nicht vollständig initialisiert.",
isError: true);
return;
}
if (_isOperationRunning)
{
return;
@@ -1509,12 +1660,7 @@ namespace ZA.CoreService.ESBCertificateManager
DialogResult confirm = MessageBox.Show(
this,
$"Container wirklich neu starten?\n\n" +
$"Ziele: {targetNames}\n\n" +
"Laut Sonic/CX-Messenger Doku (Management Application API):\n" +
"IAgentProxy.restart dieselbe Aktion wie Restart in der SMC.\n" +
$"Verbindung: appsettings ConnectionUrl + User/Pass.\n" +
"Kein stopcontainer.bat nötig.\n\n" +
"Voraussetzung: Java + Client-JARs unter SonicHome\\lib (SMC-Installation).",
$"Ziele: {targetNames}\n\n",
"ESB neu starten",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
@@ -1576,10 +1722,127 @@ namespace ZA.CoreService.ESBCertificateManager
}
}
private Task RunDeploymentAsync()
private async Task RunDeploymentAsync()
{
// Deploy/Kopieren absichtlich deaktiviert UI-Design bleibt.
return Task.CompletedTask;
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()
@@ -1680,12 +1943,29 @@ namespace ZA.CoreService.ESBCertificateManager
// ---------------------------------------------------------------
// 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 Management:",
Text = "Sonic Verbindung:",
ForeColor = MutedTextColor,
Font = new Font("Segoe UI", 8.5f, FontStyle.Bold),
AutoSize = true,
@@ -1703,11 +1983,6 @@ namespace ZA.CoreService.ESBCertificateManager
Font = new Font("Segoe UI", 9)
};
foreach (SonicConnection conn in _sonicDiscovery.Connections)
{
cmbSonicConnection.Items.Add(conn.Name);
}
lblSonicStatus = new Label
{
ForeColor = MutedTextColor,
@@ -1719,14 +1994,8 @@ namespace ZA.CoreService.ESBCertificateManager
cmbSonicConnection.SelectedIndexChanged += (_, _) => RefreshSonicStatusLine();
if (cmbSonicConnection.Items.Count > 0)
{
cmbSonicConnection.SelectedIndex = 0;
}
else
{
RefreshSonicStatusLine();
}
lblSonicStatus.Text = "Sonic-Verbindungen werden aus SQL geladen ...";
lblSonicStatus.ForeColor = MutedTextColor;
card.Controls.Add(sonicLabel);
card.Controls.Add(cmbSonicConnection);
@@ -1735,25 +2004,59 @@ namespace ZA.CoreService.ESBCertificateManager
private void RefreshSonicStatusLine()
{
if (!_sonicDiscovery.HasConnections)
if (_databaseSonicConnections.Count == 0)
{
lblSonicStatus.Text = "Keine Sonic-Verbindung in appsettings konfiguriert";
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;
}
string? selectedName = cmbSonicConnection?.SelectedItem?.ToString();
SonicConnection conn = _sonicDiscovery.Connections
.FirstOrDefault(c => string.Equals(c.Name, selectedName, StringComparison.OrdinalIgnoreCase))
?? _sonicDiscovery.Connections[0];
try
{
string connectionUrl = connection.BuildConnectionUrl();
(string home, string? javaExe) = new SonicMfApiExecutor(conn).ResolveRuntimePaths();
string javaDisplay = string.IsNullOrWhiteSpace(javaExe)
? "java=? (JavaPath in appsettings setzen)"
: $"java={javaExe}";
lblSonicStatus.Text = $"{conn.ManagementModeDisplay} | SonicHome={home} | {javaDisplay}";
lblSonicStatus.ForeColor = string.IsNullOrWhiteSpace(javaExe) ? GoldColor : GreenColor;
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)
{
}
}
}