Arbeitsstand gesichert: Deploymentcenter-Integration (D-0 bis D-5) und Einfenster-Shell
Sicherungscommit vor dem Aufraeumen des Repos, damit nachvollziehbar bleibt, welcher Stand vor der Bereinigung galt. Build gruen, 476 Tests gruen. Zwei Straenge, die sich ueber .csproj, Program.cs und appsettings.json ueberschneiden und darum gemeinsam abgelegt werden: Deploymentcenter-Integration (P3c, Plan D-0 bis D-5 code-seitig fertig): - Deploymentcenter.Client 2.5.0 als lokales Paket, Source-Mapping erweitert - DeploymentcenterOptions, DeploymentcenterErrorReporter, LicenseGate/LicenseCli - WatchdogHeartbeatService auf die Deploymentcenter-API umgestellt (version, os, checks, metrics, status stopped) - Security: MasterKeyResolver, SecretRedactor, FilePermissions - Directory.Build.props mit zentraler Version 0.1.0 (Packager-Versionsdisziplin) - deploy/: Packager-Vorlage und systemd-Unit; echte Zugangsdaten bleiben ueber .gitignore aussen vor - setup.json fuer die Erstinstallation - UMSETZUNGSPLAN-Deploymentcenter-Integration.md; ANALYSE-Linux-Portierung.md verweist auf den neuen Plan Einfenster-Shell (UI-Redesign): - ShellWindow + ShellNavModel als Seitenleisten-Shell - WindowMenuBar und WindowMenuModel entfallen - Modul- und Kernfenster auf die Shell-Einbettung angepasst Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,13 @@ using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Deploymentcenter.Client;
|
||||
using PolyTrader.App.Avalonia.Controls;
|
||||
using PolyTrader.Core;
|
||||
using PolyTrader.Core.Configuration;
|
||||
using PolyTrader.Core.Modularity;
|
||||
using PolyTrader.Core.Persistence;
|
||||
using PolyTrader.Core.Security;
|
||||
@@ -21,7 +23,7 @@ namespace PolyTrader.App.Avalonia.Views
|
||||
/// den <see cref="SettingsEditor"/> anstelle der früheren PropertyGrids.
|
||||
/// Layout vollständig in SettingsWindow.axaml.
|
||||
/// </summary>
|
||||
public partial class SettingsWindow : Window
|
||||
public partial class SettingsWindow : UserControl
|
||||
{
|
||||
private const string SettingsPath = "server_settings.xml";
|
||||
|
||||
@@ -33,20 +35,20 @@ namespace PolyTrader.App.Avalonia.Views
|
||||
private WatchdogHeartbeatService? _watchdog;
|
||||
private MullvadVpnService? _vpn;
|
||||
private TerminalLogger? _logger;
|
||||
private DeploymentcenterOptions? _dcOptions;
|
||||
|
||||
public SettingsWindow() => AvaloniaXamlLoader.Load(this);
|
||||
|
||||
public SettingsWindow(IModuleUiHost host, IAccountRepository accountRepo, TradingState state,
|
||||
TerminalLogger logger, WatchdogHeartbeatService? watchdog = null,
|
||||
MullvadVpnService? vpn = null) : this()
|
||||
MullvadVpnService? vpn = null, DeploymentcenterOptions? dcOptions = null) : this()
|
||||
{
|
||||
this.FindControl<Controls.WindowMenuBar>("menuBar")!.Attach(host, "core.settings", this);
|
||||
|
||||
_accountRepo = accountRepo;
|
||||
_state = state;
|
||||
_logger = logger;
|
||||
_watchdog = watchdog;
|
||||
_vpn = vpn;
|
||||
_dcOptions = dcOptions;
|
||||
|
||||
var grid = this.FindControl<DataGrid>("gridAccounts")!;
|
||||
grid.ItemsSource = _accounts;
|
||||
@@ -59,6 +61,8 @@ namespace PolyTrader.App.Avalonia.Views
|
||||
this.FindControl<Button>("btnOpenRouterKey")!.Click += async (_, _) => await SetOpenRouterKeyAsync();
|
||||
this.FindControl<Button>("btnWatchdogToken")!.Click += async (_, _) => await SetWatchdogTokenAsync();
|
||||
this.FindControl<Button>("btnTestWatchdog")!.Click += async (_, _) => await TestWatchdogAsync();
|
||||
this.FindControl<Button>("btnLicenseKey")!.Click += async (_, _) => await SetLicenseKeyAsync();
|
||||
this.FindControl<Button>("btnCheckLicense")!.Click += async (_, _) => await CheckLicenseAsync();
|
||||
|
||||
this.FindControl<Button>("btnAccNew")!.Click += (_, _) => AddAccount();
|
||||
this.FindControl<Button>("btnAccDelete")!.Click += async (_, _) => await DeleteAccountAsync();
|
||||
@@ -199,19 +203,8 @@ namespace PolyTrader.App.Avalonia.Views
|
||||
/// mit der üblichen umask sonst <c>644</c> an – die Datei wäre für alle lesbar. Unter Windows
|
||||
/// greift die NTFS-Vererbung, dort ist nichts zu tun.
|
||||
/// </summary>
|
||||
private void RestrictToOwner(string path)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
|
||||
try
|
||||
{
|
||||
File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Warning($"⚠️ Dateirechte für {Path.GetFileName(path)} konnten nicht gesetzt werden: {ex.Message}. " +
|
||||
"Bitte manuell prüfen (chmod 600).");
|
||||
}
|
||||
}
|
||||
private void RestrictToOwner(string path) =>
|
||||
FilePermissions.RestrictToOwner(path, msg => _logger?.Warning($"⚠️ {msg}"));
|
||||
|
||||
// ===== OpenRouter-API-Key (Supervisor) =====
|
||||
|
||||
@@ -323,5 +316,111 @@ namespace PolyTrader.App.Avalonia.Views
|
||||
btn.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Lizenz (D-2) =====
|
||||
|
||||
private async System.Threading.Tasks.Task SetLicenseKeyAsync()
|
||||
{
|
||||
bool exists = !string.IsNullOrWhiteSpace(_settings.LicenseKey);
|
||||
string? key = await DialogWindow.Prompt(this, "Deploymentcenter-Lizenzschlüssel",
|
||||
"Lizenzschlüssel eingeben.\n" +
|
||||
"Wird in der gitignorierten server_settings.xml gespeichert (leer = entfernen).\n" +
|
||||
"Wirkt erst beim nächsten Start von PolyTrader (keine Laufzeit-Revalidierung).\n" +
|
||||
(exists ? "Aktuell ist ein Schlüssel gesetzt." : "Aktuell ist KEIN Schlüssel gesetzt."));
|
||||
if (key == null) return; // Abbruch
|
||||
|
||||
try
|
||||
{
|
||||
_settings.LicenseKey = string.IsNullOrWhiteSpace(key)
|
||||
? string.Empty
|
||||
: SecretProtection.Protect(key.Trim());
|
||||
_settings.Save(SettingsPath);
|
||||
this.FindControl<SettingsEditor>("editorServer")!.Show(_settings);
|
||||
|
||||
if (_settings.LicenseKey.Length == 0)
|
||||
{
|
||||
_logger?.Info("Lizenzschlüssel entfernt.");
|
||||
Status("Lizenzschlüssel entfernt – PolyTrader startet beim nächsten Mal im eingeschränkten Modus.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool encrypted = SecretProtection.IsEncrypted(_settings.LicenseKey);
|
||||
_logger?.Info($"Lizenzschlüssel gespeichert ({(encrypted ? "verschlüsselt" : "Klartext")}).");
|
||||
Status((encrypted
|
||||
? "Lizenzschlüssel gespeichert (verschlüsselt). "
|
||||
: "Lizenzschlüssel gespeichert – ACHTUNG: ohne Master-Key im Klartext. ") +
|
||||
"Mit „Lizenz jetzt prüfen\" testen, wirkt für Module erst beim nächsten Start.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DialogWindow.Info(this, "Fehler", $"Fehler beim Speichern des Lizenzschlüssels: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prüft den GESPEICHERTEN Schlüssel gegen das Deploymentcenter und zeigt Urteil und
|
||||
/// Hardware-ID (zum Kopieren, falls Richard sie für eine Aktivierung im Admin braucht).
|
||||
/// Ungespeicherte Änderungen wirken bewusst nicht, aus demselben Grund wie beim Watchdog-Test.
|
||||
/// </summary>
|
||||
private async System.Threading.Tasks.Task CheckLicenseAsync()
|
||||
{
|
||||
if (_dcOptions == null) return;
|
||||
var btn = this.FindControl<Button>("btnCheckLicense")!;
|
||||
btn.IsEnabled = false;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_dcOptions.BaseUrl))
|
||||
{
|
||||
await DialogWindow.Info(this, "Lizenz", "Keine Deploymentcenter-URL konfiguriert (appsettings.json).");
|
||||
return;
|
||||
}
|
||||
|
||||
string key;
|
||||
try { key = SecretProtection.Unprotect(_settings.LicenseKey); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DialogWindow.Info(this, "Lizenz", $"Gespeicherter Schlüssel nicht lesbar: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
var hw = HardwareId.GetHardwareId(_dcOptions.ProjectSlug);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
await DialogWindow.Info(this, "Lizenz",
|
||||
$"Kein Lizenzschlüssel gesetzt.\n\nHardware-ID dieser Maschine:\n{hw.HardwareId}");
|
||||
Status("Kein Lizenzschlüssel gesetzt.");
|
||||
return;
|
||||
}
|
||||
|
||||
Status("Prüfe Lizenz …");
|
||||
LicenseClient.DefaultAppVersion = BuildInfo.Version;
|
||||
var client = new LicenseClient();
|
||||
var result = await client.ValidateAsync(_dcOptions.ProjectSlug, key, _dcOptions.BaseUrl, BuildInfo.Version);
|
||||
|
||||
string detail = $"Status: {result.Status}\n{result.Message}\n\nHardware-ID dieser Maschine:\n{hw.HardwareId}";
|
||||
|
||||
if (result.IsValid)
|
||||
{
|
||||
_logger?.Info($"🔑 Lizenz geprüft: gültig ({result.Status}).");
|
||||
Status($"Lizenz gültig ({result.Status}).");
|
||||
await DialogWindow.Info(this, "Lizenz gültig", detail);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.Warning($"⚠️ Lizenz geprüft: nicht gültig ({result.Status}).");
|
||||
Status($"Lizenz NICHT gültig ({result.Status}).");
|
||||
await DialogWindow.Info(this, "Lizenz nicht gültig", detail);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DialogWindow.Info(this, "Fehler", $"Lizenzprüfung fehlgeschlagen: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
btn.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user