Expand settings UI to manage systems, containers, and certificate paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-29 08:53:13 +02:00
co-authored by Cursor
parent 121a428755
commit ed6777ffed
8 changed files with 2291 additions and 501 deletions
@@ -0,0 +1,70 @@
namespace ZA.CoreService.ESBCertificateManager.Models;
public sealed class CompanyOption
{
public int CompanyId { get; init; }
public required string CompanyCode { get; init; }
public required string CompanyName { get; init; }
public override string ToString()
{
return $"{CompanyCode} — {CompanyName}";
}
}
public sealed class ContainerOption
{
public int SonicContainerId { get; init; }
public int SonicConnectionId { get; init; }
public int CompanyId { get; init; }
public required string ContainerName { get; init; }
public string? ContainerDisplayName { get; init; }
public required string CompanyCode { get; init; }
public required string ConnectionName { get; init; }
public int RestartTimeoutSeconds { get; init; }
public override string ToString()
{
string display =
string.IsNullOrWhiteSpace(ContainerDisplayName)
? ContainerName
: ContainerDisplayName;
return $"{CompanyCode} / {display} ({ConnectionName})";
}
}
public sealed class CertificateTargetOption
{
public int CertificateTargetId { get; init; }
public int SonicContainerId { get; init; }
public required string TargetDirectory { get; init; }
public required string TargetFileName { get; init; }
public bool BackupEnabled { get; init; }
public required string BackupDirectoryName { get; init; }
public required string ContainerName { get; init; }
public required string ConnectionName { get; init; }
public required string CompanyCode { get; init; }
public override string ToString()
{
return $"{CompanyCode} / {ContainerName}: {TargetDirectory}\\{TargetFileName}";
}
}
@@ -595,4 +595,730 @@ public sealed class SonicSetupRepository
reader.GetOrdinal("IsActive")) reader.GetOrdinal("IsActive"))
}; };
} }
public async Task<IReadOnlyList<CompanyOption>> GetCompaniesAsync(
CancellationToken cancellationToken = default)
{
const string sql = """
SELECT
[CompanyId],
[CompanyCode],
[CompanyName]
FROM [dbo].[Company]
WHERE [IsActive] = 1
ORDER BY [CompanyCode];
""";
List<CompanyOption> companies = [];
await using SqlConnection connection =
new(_connectionString);
await connection.OpenAsync(cancellationToken);
await using SqlCommand command =
new(sql, connection);
await using SqlDataReader reader =
await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
companies.Add(
new CompanyOption
{
CompanyId =
reader.GetInt32(
reader.GetOrdinal("CompanyId")),
CompanyCode =
reader.GetString(
reader.GetOrdinal("CompanyCode")),
CompanyName =
reader.GetString(
reader.GetOrdinal("CompanyName"))
});
}
return companies;
}
public async Task<IReadOnlyList<ContainerOption>> GetContainersAsync(
string environmentCode,
int? sonicConnectionId = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(environmentCode))
{
throw new ArgumentException(
"Der EnvironmentCode fehlt.",
nameof(environmentCode));
}
const string sql = """
SELECT
container.[SonicContainerId],
container.[SonicConnectionId],
container.[CompanyId],
container.[ContainerName],
container.[ContainerDisplayName],
container.[RestartTimeoutSeconds],
company.[CompanyCode],
connection.[ConnectionName]
FROM [dbo].[SonicContainer] AS container
INNER JOIN [dbo].[Company] AS company
ON company.[CompanyId] = container.[CompanyId]
INNER JOIN [dbo].[SonicConnection] AS connection
ON connection.[SonicConnectionId] =
container.[SonicConnectionId]
INNER JOIN [dbo].[Environment] AS environment
ON environment.[EnvironmentId] =
connection.[EnvironmentId]
WHERE container.[IsActive] = 1
AND company.[IsActive] = 1
AND connection.[IsActive] = 1
AND environment.[IsActive] = 1
AND environment.[EnvironmentCode] = @EnvironmentCode
AND
(
@SonicConnectionId IS NULL
OR container.[SonicConnectionId] = @SonicConnectionId
)
ORDER BY
company.[CompanyCode],
container.[ContainerName];
""";
List<ContainerOption> containers = [];
await using SqlConnection connection =
new(_connectionString);
await connection.OpenAsync(cancellationToken);
await using SqlCommand command =
new(sql, connection);
command.Parameters.Add(
new SqlParameter(
"@EnvironmentCode",
SqlDbType.NVarChar,
30)
{
Value = environmentCode
.Trim()
.ToUpperInvariant()
});
command.Parameters.Add(
new SqlParameter(
"@SonicConnectionId",
SqlDbType.Int)
{
Value = sonicConnectionId is null or <= 0
? DBNull.Value
: sonicConnectionId.Value
});
await using SqlDataReader reader =
await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
int displayOrdinal =
reader.GetOrdinal("ContainerDisplayName");
containers.Add(
new ContainerOption
{
SonicContainerId =
reader.GetInt32(
reader.GetOrdinal("SonicContainerId")),
SonicConnectionId =
reader.GetInt32(
reader.GetOrdinal("SonicConnectionId")),
CompanyId =
reader.GetInt32(
reader.GetOrdinal("CompanyId")),
ContainerName =
reader.GetString(
reader.GetOrdinal("ContainerName")),
ContainerDisplayName =
reader.IsDBNull(displayOrdinal)
? null
: reader.GetString(displayOrdinal),
RestartTimeoutSeconds =
reader.GetInt32(
reader.GetOrdinal("RestartTimeoutSeconds")),
CompanyCode =
reader.GetString(
reader.GetOrdinal("CompanyCode")),
ConnectionName =
reader.GetString(
reader.GetOrdinal("ConnectionName"))
});
}
return containers;
}
public async Task<IReadOnlyList<CertificateTargetOption>>
GetCertificateTargetsAsync(
string environmentCode,
int? sonicContainerId = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(environmentCode))
{
throw new ArgumentException(
"Der EnvironmentCode fehlt.",
nameof(environmentCode));
}
const string sql = """
SELECT
target.[CertificateTargetId],
target.[SonicContainerId],
target.[TargetDirectory],
target.[TargetFileName],
target.[BackupEnabled],
target.[BackupDirectoryName],
container.[ContainerName],
connection.[ConnectionName],
company.[CompanyCode]
FROM [dbo].[CertificateTarget] AS target
INNER JOIN [dbo].[SonicContainer] AS container
ON container.[SonicContainerId] =
target.[SonicContainerId]
INNER JOIN [dbo].[SonicConnection] AS connection
ON connection.[SonicConnectionId] =
container.[SonicConnectionId]
INNER JOIN [dbo].[Company] AS company
ON company.[CompanyId] = container.[CompanyId]
INNER JOIN [dbo].[Environment] AS environment
ON environment.[EnvironmentId] =
connection.[EnvironmentId]
WHERE target.[IsActive] = 1
AND container.[IsActive] = 1
AND connection.[IsActive] = 1
AND company.[IsActive] = 1
AND environment.[IsActive] = 1
AND environment.[EnvironmentCode] = @EnvironmentCode
AND
(
@SonicContainerId IS NULL
OR target.[SonicContainerId] = @SonicContainerId
)
ORDER BY
company.[CompanyCode],
container.[ContainerName],
target.[TargetDirectory],
target.[TargetFileName];
""";
List<CertificateTargetOption> targets = [];
await using SqlConnection connection =
new(_connectionString);
await connection.OpenAsync(cancellationToken);
await using SqlCommand command =
new(sql, connection);
command.Parameters.Add(
new SqlParameter(
"@EnvironmentCode",
SqlDbType.NVarChar,
30)
{
Value = environmentCode
.Trim()
.ToUpperInvariant()
});
command.Parameters.Add(
new SqlParameter(
"@SonicContainerId",
SqlDbType.Int)
{
Value = sonicContainerId is null or <= 0
? DBNull.Value
: sonicContainerId.Value
});
await using SqlDataReader reader =
await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
targets.Add(
new CertificateTargetOption
{
CertificateTargetId =
reader.GetInt32(
reader.GetOrdinal("CertificateTargetId")),
SonicContainerId =
reader.GetInt32(
reader.GetOrdinal("SonicContainerId")),
TargetDirectory =
reader.GetString(
reader.GetOrdinal("TargetDirectory")),
TargetFileName =
reader.GetString(
reader.GetOrdinal("TargetFileName")),
BackupEnabled =
reader.GetBoolean(
reader.GetOrdinal("BackupEnabled")),
BackupDirectoryName =
reader.GetString(
reader.GetOrdinal("BackupDirectoryName")),
ContainerName =
reader.GetString(
reader.GetOrdinal("ContainerName")),
ConnectionName =
reader.GetString(
reader.GetOrdinal("ConnectionName")),
CompanyCode =
reader.GetString(
reader.GetOrdinal("CompanyCode"))
});
}
return targets;
}
public async Task<int> AddSonicConnectionAsync(
string environmentCode,
string connectionName,
string managementHost,
int managementPort,
string domainName,
string connectionProtocol,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(environmentCode))
{
throw new ArgumentException(
"Der EnvironmentCode fehlt.",
nameof(environmentCode));
}
string name = RequireTrimmed(
connectionName,
150,
"ConnectionName");
string host = RequireTrimmed(
managementHost,
255,
"ManagementHost");
string domain = RequireTrimmed(
domainName,
150,
"DomainName");
string protocol = string.IsNullOrWhiteSpace(connectionProtocol)
? "tcp"
: connectionProtocol.Trim().TrimEnd(':', '/');
if (managementPort is < 1 or > 65535)
{
throw new ArgumentOutOfRangeException(
nameof(managementPort),
"Der ManagementPort muss zwischen 1 und 65535 liegen.");
}
const string sql = """
INSERT INTO [dbo].[SonicConnection]
(
[EnvironmentId],
[ConnectionName],
[ManagementHost],
[ManagementPort],
[DomainName],
[ConnectionProtocol],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
OUTPUT INSERTED.[SonicConnectionId]
SELECT
environment.[EnvironmentId],
@ConnectionName,
@ManagementHost,
@ManagementPort,
@DomainName,
@ConnectionProtocol,
1,
SYSUTCDATETIME(),
SUSER_SNAME()
FROM [dbo].[Environment] AS environment
WHERE environment.[EnvironmentCode] = @EnvironmentCode
AND environment.[IsActive] = 1;
""";
await using SqlConnection connection =
new(_connectionString);
await connection.OpenAsync(cancellationToken);
await using SqlCommand command =
new(sql, connection);
command.Parameters.Add(
new SqlParameter(
"@EnvironmentCode",
SqlDbType.NVarChar,
30)
{
Value = environmentCode
.Trim()
.ToUpperInvariant()
});
command.Parameters.Add(
new SqlParameter(
"@ConnectionName",
SqlDbType.NVarChar,
150)
{
Value = name
});
command.Parameters.Add(
new SqlParameter(
"@ManagementHost",
SqlDbType.NVarChar,
255)
{
Value = host
});
command.Parameters.Add(
new SqlParameter(
"@ManagementPort",
SqlDbType.Int)
{
Value = managementPort
});
command.Parameters.Add(
new SqlParameter(
"@DomainName",
SqlDbType.NVarChar,
150)
{
Value = domain
});
command.Parameters.Add(
new SqlParameter(
"@ConnectionProtocol",
SqlDbType.NVarChar,
20)
{
Value = protocol
});
object? result =
await command.ExecuteScalarAsync(cancellationToken);
if (result is null || result is DBNull)
{
throw new InvalidOperationException(
$"Die Umgebung '{environmentCode}' wurde nicht gefunden " +
"oder die Sonic-Verbindung konnte nicht angelegt werden.");
}
return Convert.ToInt32(result);
}
public async Task<int> AddSonicContainerAsync(
int companyId,
int sonicConnectionId,
string containerName,
string? containerDisplayName,
int restartTimeoutSeconds,
CancellationToken cancellationToken = default)
{
if (companyId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(companyId));
}
if (sonicConnectionId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(sonicConnectionId));
}
string name = RequireTrimmed(
containerName,
250,
"ContainerName");
string? display =
string.IsNullOrWhiteSpace(containerDisplayName)
? null
: containerDisplayName.Trim();
if (display is { Length: > 250 })
{
throw new ArgumentException(
"Der Anzeigename darf maximal 250 Zeichen enthalten.",
nameof(containerDisplayName));
}
if (restartTimeoutSeconds is < 10 or > 3600)
{
throw new ArgumentOutOfRangeException(
nameof(restartTimeoutSeconds),
"Timeout muss zwischen 10 und 3600 Sekunden liegen.");
}
const string sql = """
INSERT INTO [dbo].[SonicContainer]
(
[CompanyId],
[SonicConnectionId],
[ContainerName],
[ContainerDisplayName],
[RestartTimeoutSeconds],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
OUTPUT INSERTED.[SonicContainerId]
VALUES
(
@CompanyId,
@SonicConnectionId,
@ContainerName,
@ContainerDisplayName,
@RestartTimeoutSeconds,
1,
SYSUTCDATETIME(),
SUSER_SNAME()
);
""";
await using SqlConnection connection =
new(_connectionString);
await connection.OpenAsync(cancellationToken);
await using SqlCommand command =
new(sql, connection);
command.Parameters.Add(
new SqlParameter("@CompanyId", SqlDbType.Int)
{
Value = companyId
});
command.Parameters.Add(
new SqlParameter("@SonicConnectionId", SqlDbType.Int)
{
Value = sonicConnectionId
});
command.Parameters.Add(
new SqlParameter(
"@ContainerName",
SqlDbType.NVarChar,
250)
{
Value = name
});
command.Parameters.Add(
new SqlParameter(
"@ContainerDisplayName",
SqlDbType.NVarChar,
250)
{
Value = display is null
? DBNull.Value
: display
});
command.Parameters.Add(
new SqlParameter(
"@RestartTimeoutSeconds",
SqlDbType.Int)
{
Value = restartTimeoutSeconds
});
object? result =
await command.ExecuteScalarAsync(cancellationToken);
if (result is null || result is DBNull)
{
throw new InvalidOperationException(
"Der Container konnte nicht angelegt werden.");
}
return Convert.ToInt32(result);
}
public async Task<int> AddCertificateTargetAsync(
int sonicContainerId,
string targetDirectory,
string targetFileName,
bool backupEnabled,
string backupDirectoryName,
CancellationToken cancellationToken = default)
{
if (sonicContainerId <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(sonicContainerId));
}
string directory = RequireTrimmed(
targetDirectory,
500,
"TargetDirectory");
string fileName = RequireTrimmed(
targetFileName,
260,
"TargetFileName");
string backupName = string.IsNullOrWhiteSpace(backupDirectoryName)
? "Backup"
: backupDirectoryName.Trim();
if (backupName.Length > 100)
{
throw new ArgumentException(
"Der Backup-Ordnername darf maximal 100 Zeichen enthalten.",
nameof(backupDirectoryName));
}
const string sql = """
INSERT INTO [dbo].[CertificateTarget]
(
[SonicContainerId],
[TargetDirectory],
[TargetFileName],
[BackupEnabled],
[BackupDirectoryName],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
OUTPUT INSERTED.[CertificateTargetId]
VALUES
(
@SonicContainerId,
@TargetDirectory,
@TargetFileName,
@BackupEnabled,
@BackupDirectoryName,
1,
SYSUTCDATETIME(),
SUSER_SNAME()
);
""";
await using SqlConnection connection =
new(_connectionString);
await connection.OpenAsync(cancellationToken);
await using SqlCommand command =
new(sql, connection);
command.Parameters.Add(
new SqlParameter("@SonicContainerId", SqlDbType.Int)
{
Value = sonicContainerId
});
command.Parameters.Add(
new SqlParameter(
"@TargetDirectory",
SqlDbType.NVarChar,
500)
{
Value = directory
});
command.Parameters.Add(
new SqlParameter(
"@TargetFileName",
SqlDbType.NVarChar,
260)
{
Value = fileName
});
command.Parameters.Add(
new SqlParameter("@BackupEnabled", SqlDbType.Bit)
{
Value = backupEnabled
});
command.Parameters.Add(
new SqlParameter(
"@BackupDirectoryName",
SqlDbType.NVarChar,
100)
{
Value = backupName
});
object? result =
await command.ExecuteScalarAsync(cancellationToken);
if (result is null || result is DBNull)
{
throw new InvalidOperationException(
"Das Zertifikat-Ziel konnte nicht angelegt werden.");
}
return Convert.ToInt32(result);
}
private static string RequireTrimmed(
string? value,
int maxLength,
string fieldName)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException(
$"'{fieldName}' fehlt.");
}
string trimmed = value.Trim();
if (trimmed.Length > maxLength)
{
throw new ArgumentException(
$"'{fieldName}' darf maximal {maxLength} Zeichen enthalten.");
}
return trimmed;
}
} }
@@ -0,0 +1,235 @@
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Setup;
public sealed class AddContainerForm : Form
{
private readonly SetupCoordinator _coordinator;
private readonly ComboBox _cmbCompany;
private readonly ComboBox _cmbSystem;
private readonly TextBox _txtName;
private readonly TextBox _txtDisplay;
private readonly TextBox _txtTimeout;
private readonly Button _btnSave;
private bool _running;
public int? CreatedSonicContainerId { get; private set; }
public AddContainerForm(
SetupCoordinator coordinator,
int? preferredSonicConnectionId = null)
{
_coordinator = coordinator;
Text = "Container hinzufügen";
StartPosition = FormStartPosition.CenterParent;
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
ShowInTaskbar = false;
ClientSize = new Size(540, 470);
BackColor = SettingsUi.Background;
ForeColor = SettingsUi.Text;
Font = new Font("Segoe UI", 10f);
Label title = new()
{
Text = "Neuer Container",
Location = new Point(28, 22),
AutoSize = true,
Font = new Font("Segoe UI Semibold", 18f, FontStyle.Bold),
ForeColor = SettingsUi.Text
};
Label subtitle = new()
{
Text =
"Container einer Company und einem Sonic-System zuordnen.",
Location = new Point(30, 58),
AutoSize = true,
ForeColor = SettingsUi.Muted
};
Panel card = SettingsUi.CreateCard();
card.Location = new Point(28, 95);
card.Size = new Size(484, 290);
card.Paint += SettingsUi.PaintCardBorder;
_cmbCompany = SettingsUi.CreateComboBox();
_cmbSystem = SettingsUi.CreateComboBox();
_txtName = SettingsUi.CreateTextBox();
_txtDisplay = SettingsUi.CreateTextBox();
_txtTimeout = SettingsUi.CreateTextBox();
_txtTimeout.Text = "180";
AddField(card, "Company", _cmbCompany, 18, 18, 440);
AddField(card, "Sonic-System", _cmbSystem, 18, 78, 440);
AddField(card, "Container-Name", _txtName, 18, 138, 440);
AddField(card, "Anzeigename (optional)", _txtDisplay, 18, 198, 280);
AddField(card, "Timeout (s)", _txtTimeout, 318, 198, 140);
_btnSave = SettingsUi.CreatePrimaryButton("Container anlegen");
_btnSave.Location = new Point(292, 405);
_btnSave.Size = new Size(220, 40);
_btnSave.Click += async (_, _) => await SaveAsync();
Button cancel = SettingsUi.CreateGhostButton("Abbrechen");
cancel.Location = new Point(170, 405);
cancel.Size = new Size(110, 40);
cancel.Click += (_, _) =>
{
DialogResult = DialogResult.Cancel;
Close();
};
Controls.Add(title);
Controls.Add(subtitle);
Controls.Add(card);
Controls.Add(cancel);
Controls.Add(_btnSave);
Shown += async (_, _) =>
await LoadLookupsAsync(preferredSonicConnectionId);
}
private async Task LoadLookupsAsync(int? preferredSonicConnectionId)
{
try
{
IReadOnlyList<CompanyOption> companies =
await _coordinator.LoadCompaniesAsync();
IReadOnlyList<SonicSystemOption> systems =
await _coordinator.LoadSystemsAsync();
_cmbCompany.Items.Clear();
foreach (CompanyOption company in companies)
{
_cmbCompany.Items.Add(company);
}
if (_cmbCompany.Items.Count > 0)
{
_cmbCompany.SelectedIndex = 0;
}
_cmbSystem.Items.Clear();
int selected = 0;
for (int index = 0; index < systems.Count; index++)
{
_cmbSystem.Items.Add(systems[index]);
if (preferredSonicConnectionId is int id
&& systems[index].SonicConnectionId == id)
{
selected = index;
}
}
if (_cmbSystem.Items.Count > 0)
{
_cmbSystem.SelectedIndex = selected;
}
}
catch (Exception ex)
{
MessageBox.Show(
this,
ex.Message,
"Container",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
private async Task SaveAsync()
{
if (_running)
{
return;
}
if (_cmbCompany.SelectedItem is not CompanyOption company)
{
MessageBox.Show(
this,
"Bitte eine Company wählen.",
"Container",
MessageBoxButtons.OK,
MessageBoxIcon.Warning);
return;
}
if (_cmbSystem.SelectedItem is not SonicSystemOption system)
{
MessageBox.Show(
this,
"Bitte ein Sonic-System wählen.",
"Container",
MessageBoxButtons.OK,
MessageBoxIcon.Warning);
return;
}
if (!int.TryParse(_txtTimeout.Text.Trim(), out int timeout))
{
MessageBox.Show(
this,
"Bitte einen gültigen Timeout eingeben.",
"Container",
MessageBoxButtons.OK,
MessageBoxIcon.Warning);
return;
}
_running = true;
_btnSave.Enabled = false;
Cursor = Cursors.WaitCursor;
try
{
CreatedSonicContainerId =
await _coordinator.AddSonicContainerAsync(
company.CompanyId,
system.SonicConnectionId,
_txtName.Text,
_txtDisplay.Text,
timeout);
DialogResult = DialogResult.OK;
Close();
}
catch (Exception ex)
{
MessageBox.Show(
this,
ex.Message,
"Container",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
finally
{
_running = false;
_btnSave.Enabled = true;
Cursor = Cursors.Default;
}
}
private static void AddField(
Control parent,
string label,
Control input,
int x,
int y,
int width)
{
Label fieldLabel = SettingsUi.CreateFieldLabel(label);
fieldLabel.Location = new Point(x, y);
input.Location = new Point(x, y + 22);
input.Width = width;
parent.Controls.Add(fieldLabel);
parent.Controls.Add(input);
}
}
@@ -0,0 +1,160 @@
namespace ZA.CoreService.ESBCertificateManager.Setup;
public sealed class AddSonicSystemForm : Form
{
private readonly SetupCoordinator _coordinator;
private readonly TextBox _txtName;
private readonly TextBox _txtHost;
private readonly TextBox _txtPort;
private readonly TextBox _txtDomain;
private readonly ComboBox _cmbProtocol;
private readonly Button _btnSave;
private bool _running;
public int? CreatedSonicConnectionId { get; private set; }
public AddSonicSystemForm(SetupCoordinator coordinator)
{
_coordinator = coordinator;
Text = "Sonic-System hinzufügen";
StartPosition = FormStartPosition.CenterParent;
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
ShowInTaskbar = false;
ClientSize = new Size(520, 430);
BackColor = SettingsUi.Background;
ForeColor = SettingsUi.Text;
Font = new Font("Segoe UI", 10f);
Label title = new()
{
Text = "Neues Sonic-System",
Location = new Point(28, 22),
AutoSize = true,
Font = new Font("Segoe UI Semibold", 18f, FontStyle.Bold),
ForeColor = SettingsUi.Text
};
Label subtitle = new()
{
Text =
$"Umgebung {_coordinator.Settings.EnvironmentCode} · " +
"Management-Verbindung für Container-Neustarts",
Location = new Point(30, 58),
AutoSize = true,
ForeColor = SettingsUi.Muted
};
Panel card = SettingsUi.CreateCard();
card.Location = new Point(28, 95);
card.Size = new Size(464, 250);
card.Paint += SettingsUi.PaintCardBorder;
_txtName = SettingsUi.CreateTextBox();
_txtHost = SettingsUi.CreateTextBox();
_txtPort = SettingsUi.CreateTextBox();
_txtDomain = SettingsUi.CreateTextBox();
_cmbProtocol = SettingsUi.CreateComboBox();
_cmbProtocol.Items.AddRange(["tcp", "ssl"]);
_cmbProtocol.SelectedIndex = 0;
_txtPort.Text = "13070";
AddField(card, "Verbindungsname", _txtName, 18, 18, 420);
AddField(card, "Management-Host", _txtHost, 18, 78, 280);
AddField(card, "Port", _txtPort, 316, 78, 122);
AddField(card, "Domain", _txtDomain, 18, 138, 420);
AddField(card, "Protokoll", _cmbProtocol, 18, 198, 160);
_btnSave = SettingsUi.CreatePrimaryButton("System anlegen");
_btnSave.Location = new Point(292, 365);
_btnSave.Size = new Size(200, 40);
_btnSave.Click += async (_, _) => await SaveAsync();
Button cancel = SettingsUi.CreateGhostButton("Abbrechen");
cancel.Location = new Point(170, 365);
cancel.Size = new Size(110, 40);
cancel.Click += (_, _) =>
{
DialogResult = DialogResult.Cancel;
Close();
};
Controls.Add(title);
Controls.Add(subtitle);
Controls.Add(card);
Controls.Add(cancel);
Controls.Add(_btnSave);
AcceptButton = _btnSave;
CancelButton = cancel;
}
private async Task SaveAsync()
{
if (_running)
{
return;
}
if (!int.TryParse(_txtPort.Text.Trim(), out int port))
{
MessageBox.Show(
this,
"Bitte einen gültigen Port eingeben.",
"Sonic-System",
MessageBoxButtons.OK,
MessageBoxIcon.Warning);
return;
}
_running = true;
_btnSave.Enabled = false;
Cursor = Cursors.WaitCursor;
try
{
CreatedSonicConnectionId =
await _coordinator.AddSonicConnectionAsync(
_txtName.Text,
_txtHost.Text,
port,
_txtDomain.Text,
_cmbProtocol.SelectedItem?.ToString() ?? "tcp");
DialogResult = DialogResult.OK;
Close();
}
catch (Exception ex)
{
MessageBox.Show(
this,
ex.Message,
"Sonic-System",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
finally
{
_running = false;
_btnSave.Enabled = true;
Cursor = Cursors.Default;
}
}
private static void AddField(
Control parent,
string label,
Control input,
int x,
int y,
int width)
{
Label fieldLabel = SettingsUi.CreateFieldLabel(label);
fieldLabel.Location = new Point(x, y);
input.Location = new Point(x, y + 22);
input.Width = width;
parent.Controls.Add(fieldLabel);
parent.Controls.Add(input);
}
}
@@ -0,0 +1,210 @@
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Setup;
public sealed class AddTargetPathForm : Form
{
private readonly SetupCoordinator _coordinator;
private readonly ComboBox _cmbContainer;
private readonly TextBox _txtDirectory;
private readonly TextBox _txtFileName;
private readonly TextBox _txtBackupFolder;
private readonly CheckBox _chkBackup;
private readonly Button _btnSave;
private bool _running;
public int? CreatedCertificateTargetId { get; private set; }
public AddTargetPathForm(
SetupCoordinator coordinator,
int? preferredSonicContainerId = null)
{
_coordinator = coordinator;
Text = "Zertifikat-Pfad hinzufügen";
StartPosition = FormStartPosition.CenterParent;
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
ShowInTaskbar = false;
ClientSize = new Size(560, 450);
BackColor = SettingsUi.Background;
ForeColor = SettingsUi.Text;
Font = new Font("Segoe UI", 10f);
Label title = new()
{
Text = "Neuer Zertifikat-Pfad",
Location = new Point(28, 22),
AutoSize = true,
Font = new Font("Segoe UI Semibold", 18f, FontStyle.Bold),
ForeColor = SettingsUi.Text
};
Label subtitle = new()
{
Text =
"Zielverzeichnis und Dateiname für die Zertifikatsablage.",
Location = new Point(30, 58),
AutoSize = true,
ForeColor = SettingsUi.Muted
};
Panel card = SettingsUi.CreateCard();
card.Location = new Point(28, 95);
card.Size = new Size(504, 270);
card.Paint += SettingsUi.PaintCardBorder;
_cmbContainer = SettingsUi.CreateComboBox();
_txtDirectory = SettingsUi.CreateTextBox();
_txtFileName = SettingsUi.CreateTextBox();
_txtBackupFolder = SettingsUi.CreateTextBox();
_txtBackupFolder.Text = "Backup";
_txtFileName.Text = "server.crt";
_chkBackup = new CheckBox
{
Text = "Backup vor dem Überschreiben",
AutoSize = true,
Checked = true,
ForeColor = SettingsUi.Text,
Font = new Font("Segoe UI", 9.5f)
};
AddField(card, "Container", _cmbContainer, 18, 18, 460);
AddField(card, "Zielverzeichnis", _txtDirectory, 18, 78, 460);
AddField(card, "Dateiname", _txtFileName, 18, 138, 220);
AddField(card, "Backup-Ordner", _txtBackupFolder, 258, 138, 220);
_chkBackup.Location = new Point(18, 210);
card.Controls.Add(_chkBackup);
_btnSave = SettingsUi.CreatePrimaryButton("Pfad anlegen");
_btnSave.Location = new Point(312, 385);
_btnSave.Size = new Size(220, 40);
_btnSave.Click += async (_, _) => await SaveAsync();
Button cancel = SettingsUi.CreateGhostButton("Abbrechen");
cancel.Location = new Point(190, 385);
cancel.Size = new Size(110, 40);
cancel.Click += (_, _) =>
{
DialogResult = DialogResult.Cancel;
Close();
};
Controls.Add(title);
Controls.Add(subtitle);
Controls.Add(card);
Controls.Add(cancel);
Controls.Add(_btnSave);
Shown += async (_, _) =>
await LoadContainersAsync(preferredSonicContainerId);
}
private async Task LoadContainersAsync(int? preferredSonicContainerId)
{
try
{
IReadOnlyList<ContainerOption> containers =
await _coordinator.LoadContainersAsync();
_cmbContainer.Items.Clear();
int selected = 0;
for (int index = 0; index < containers.Count; index++)
{
_cmbContainer.Items.Add(containers[index]);
if (preferredSonicContainerId is int id
&& containers[index].SonicContainerId == id)
{
selected = index;
}
}
if (_cmbContainer.Items.Count > 0)
{
_cmbContainer.SelectedIndex = selected;
}
}
catch (Exception ex)
{
MessageBox.Show(
this,
ex.Message,
"Zertifikat-Pfad",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
private async Task SaveAsync()
{
if (_running)
{
return;
}
if (_cmbContainer.SelectedItem is not ContainerOption container)
{
MessageBox.Show(
this,
"Bitte einen Container wählen.",
"Zertifikat-Pfad",
MessageBoxButtons.OK,
MessageBoxIcon.Warning);
return;
}
_running = true;
_btnSave.Enabled = false;
Cursor = Cursors.WaitCursor;
try
{
CreatedCertificateTargetId =
await _coordinator.AddCertificateTargetAsync(
container.SonicContainerId,
_txtDirectory.Text,
_txtFileName.Text,
_chkBackup.Checked,
_txtBackupFolder.Text);
DialogResult = DialogResult.OK;
Close();
}
catch (Exception ex)
{
MessageBox.Show(
this,
ex.Message,
"Zertifikat-Pfad",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
finally
{
_running = false;
_btnSave.Enabled = true;
Cursor = Cursors.Default;
}
}
private static void AddField(
Control parent,
string label,
Control input,
int x,
int y,
int width)
{
Label fieldLabel = SettingsUi.CreateFieldLabel(label);
fieldLabel.Location = new Point(x, y);
input.Location = new Point(x, y + 22);
input.Width = width;
parent.Controls.Add(fieldLabel);
parent.Controls.Add(input);
}
}
@@ -0,0 +1,156 @@
namespace ZA.CoreService.ESBCertificateManager.Setup;
internal static class SettingsUi
{
public static readonly Color Background =
Color.FromArgb(10, 18, 34);
public static readonly Color Sidebar =
Color.FromArgb(7, 23, 45);
public static readonly Color Card =
Color.FromArgb(16, 38, 65);
public static readonly Color CardHover =
Color.FromArgb(23, 55, 94);
public static readonly Color Border =
Color.FromArgb(36, 74, 117);
public static readonly Color Text =
Color.FromArgb(241, 245, 249);
public static readonly Color Muted =
Color.FromArgb(169, 184, 200);
public static readonly Color Gold =
Color.FromArgb(208, 171, 57);
public static readonly Color Blue =
Color.FromArgb(0, 110, 182);
public static readonly Color Green =
Color.FromArgb(60, 203, 127);
public static readonly Color Red =
Color.FromArgb(239, 106, 106);
public static Button CreatePrimaryButton(string text)
{
Button button = new()
{
Text = text,
BackColor = Gold,
ForeColor = Color.FromArgb(30, 25, 10),
FlatStyle = FlatStyle.Flat,
Font = new Font("Segoe UI Semibold", 9f, FontStyle.Bold),
Cursor = Cursors.Hand,
Height = 38
};
button.FlatAppearance.BorderSize = 0;
button.FlatAppearance.MouseOverBackColor =
Color.FromArgb(226, 196, 93);
return button;
}
public static Button CreateGhostButton(string text)
{
Button button = new()
{
Text = text,
BackColor = Card,
ForeColor = Text,
FlatStyle = FlatStyle.Flat,
Font = new Font("Segoe UI Semibold", 9f, FontStyle.Bold),
Cursor = Cursors.Hand,
Height = 38
};
button.FlatAppearance.BorderColor = Border;
button.FlatAppearance.BorderSize = 1;
button.FlatAppearance.MouseOverBackColor = CardHover;
return button;
}
public static TextBox CreateTextBox()
{
return new TextBox
{
BackColor = Color.FromArgb(12, 28, 48),
ForeColor = Text,
BorderStyle = BorderStyle.FixedSingle,
Font = new Font("Segoe UI", 10f),
Height = 30
};
}
public static ComboBox CreateComboBox()
{
return new ComboBox
{
BackColor = Color.FromArgb(12, 28, 48),
ForeColor = Text,
FlatStyle = FlatStyle.Flat,
DropDownStyle = ComboBoxStyle.DropDownList,
Font = new Font("Segoe UI", 10f),
Height = 30
};
}
public static Label CreateFieldLabel(string text)
{
return new Label
{
Text = text,
AutoSize = true,
ForeColor = Muted,
Font = new Font("Segoe UI Semibold", 8.5f, FontStyle.Bold)
};
}
public static Panel CreateCard()
{
return new Panel
{
BackColor = Card,
Padding = new Padding(18)
};
}
public static ListView CreateListView()
{
ListView list = new()
{
View = View.Details,
FullRowSelect = true,
MultiSelect = false,
HeaderStyle = ColumnHeaderStyle.Nonclickable,
BorderStyle = BorderStyle.FixedSingle,
BackColor = Color.FromArgb(12, 28, 48),
ForeColor = Text,
Font = new Font("Segoe UI", 9.5f),
HideSelection = false
};
return list;
}
public static void PaintCardBorder(
object? sender,
PaintEventArgs e)
{
if (sender is not Control control)
{
return;
}
using Pen pen = new(Border, 1);
Rectangle bounds = control.ClientRectangle;
bounds.Width -= 1;
bounds.Height -= 1;
e.Graphics.DrawRectangle(pen, bounds);
}
}
@@ -325,6 +325,85 @@ public sealed class SetupCoordinator
cancellationToken); cancellationToken);
} }
public Task<IReadOnlyList<CompanyOption>> LoadCompaniesAsync(
CancellationToken cancellationToken = default)
{
return _setupRepository.GetCompaniesAsync(cancellationToken);
}
public Task<IReadOnlyList<ContainerOption>> LoadContainersAsync(
int? sonicConnectionId = null,
CancellationToken cancellationToken = default)
{
return _setupRepository.GetContainersAsync(
_settings.EnvironmentCode,
sonicConnectionId,
cancellationToken);
}
public Task<IReadOnlyList<CertificateTargetOption>>
LoadCertificateTargetsAsync(
int? sonicContainerId = null,
CancellationToken cancellationToken = default)
{
return _setupRepository.GetCertificateTargetsAsync(
_settings.EnvironmentCode,
sonicContainerId,
cancellationToken);
}
public Task<int> AddSonicConnectionAsync(
string connectionName,
string managementHost,
int managementPort,
string domainName,
string connectionProtocol,
CancellationToken cancellationToken = default)
{
return _setupRepository.AddSonicConnectionAsync(
_settings.EnvironmentCode,
connectionName,
managementHost,
managementPort,
domainName,
connectionProtocol,
cancellationToken);
}
public Task<int> AddSonicContainerAsync(
int companyId,
int sonicConnectionId,
string containerName,
string? containerDisplayName,
int restartTimeoutSeconds,
CancellationToken cancellationToken = default)
{
return _setupRepository.AddSonicContainerAsync(
companyId,
sonicConnectionId,
containerName,
containerDisplayName,
restartTimeoutSeconds,
cancellationToken);
}
public Task<int> AddCertificateTargetAsync(
int sonicContainerId,
string targetDirectory,
string targetFileName,
bool backupEnabled,
string backupDirectoryName,
CancellationToken cancellationToken = default)
{
return _setupRepository.AddCertificateTargetAsync(
sonicContainerId,
targetDirectory,
targetFileName,
backupEnabled,
backupDirectoryName,
cancellationToken);
}
public void SaveSelection( public void SaveSelection(
SonicSystemOption system, SonicSystemOption system,
SonicCredentialProfile profile) SonicCredentialProfile profile)
File diff suppressed because it is too large Load Diff