Watchdog-Heartbeat + LicenseLabrador-Lizenzpruefung eingebunden
Slices W-A/W-B/W-C und L-A aus UMSETZUNGSPLAN-Watchdog-LicenseLabrador-Integration. Watchdog (Dead-Man's-Switch, externer Server): - WatchdogHeartbeatService als BackgroundService + DI-Singleton; jeder Sendeversuch gekapselt, ein Ausfall des Watchdogs beeintraechtigt PolyTrader nie. - Eigene Implementierung statt Test-Client des Fremdprojekts: TLS-Pruefung bleibt aktiv, http:// nur fuer localhost (Agent-Token nicht im Klartext ins Netz). - Status aus dem App-Log abgeleitet (Error mit 5-Minuten-Sticky-Fenster, entprellt), Lifecycle-Events started/stopping. - Konfiguration in ServerSettings; Agent-Token [Browsable(false)] mit maskierter Eingabe + Statusanzeige, bei gesetztem Master-Key verschluesselt (enc:v1:). Lizenz (LicenseLabrador, Ed25519): - LicenseGate.RunStartupGate prueft beim Start; bei ungueltiger Lizenz wird die Modulliste leer gebaut, sodass nur die Core-Shell (Terminal/Einstellungen) startet. Bewusst kein Environment.Exit - ein Trading-Bot darf nicht mitten im Lauf hart sterben. TamperSuspected gilt als nicht nutzbar. - LicenseDialog (partial + .Designer.cs) fuer Start- und Verwalten-Modus, mit Hardware-ID zum Kopieren; Smoke-UI konstruiert beide Modi headless. - Master-Key wird jetzt VOR dem Host-Build geladen, da auch der Lizenzschluessel entschluesselt werden muss; derselbe TerminalLogger wird als Singleton weitergereicht, damit die Startmeldungen im Terminal-Fenster erscheinen. Der Lizenz-SDK-Client wird per Cross-Repo-Projektreferenz auf ..\..\LicenseLabrador eingebunden, damit SDK-Fixes ohne Kopie einfliessen. 484 Zeilen Tests fuer den Heartbeat; Suite gruen (438 Tests). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Generated
+157
@@ -0,0 +1,157 @@
|
||||
namespace PolyTraderSharp.Ui
|
||||
{
|
||||
partial class LicenseDialog
|
||||
{
|
||||
/// <summary>Erforderliche Designer-Variable.</summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Vom Windows Form-Designer generierter Code
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
lblHeadline = new System.Windows.Forms.Label();
|
||||
lblStatus = new System.Windows.Forms.Label();
|
||||
lblHwCaption = new System.Windows.Forms.Label();
|
||||
txtHardwareId = new System.Windows.Forms.TextBox();
|
||||
btnCopyHwId = new System.Windows.Forms.Button();
|
||||
lblKeyCaption = new System.Windows.Forms.Label();
|
||||
txtKey = new System.Windows.Forms.TextBox();
|
||||
btnValidate = new System.Windows.Forms.Button();
|
||||
lblResult = new System.Windows.Forms.Label();
|
||||
btnLimited = new System.Windows.Forms.Button();
|
||||
SuspendLayout();
|
||||
//
|
||||
// lblHeadline
|
||||
//
|
||||
lblHeadline.AutoSize = true;
|
||||
lblHeadline.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold);
|
||||
lblHeadline.Location = new System.Drawing.Point(16, 14);
|
||||
lblHeadline.Name = "lblHeadline";
|
||||
lblHeadline.Size = new System.Drawing.Size(316, 17);
|
||||
lblHeadline.TabIndex = 0;
|
||||
lblHeadline.Text = "Für PolyTrader wird eine gültige Lizenz benötigt.";
|
||||
//
|
||||
// lblStatus
|
||||
//
|
||||
lblStatus.Location = new System.Drawing.Point(16, 40);
|
||||
lblStatus.Name = "lblStatus";
|
||||
lblStatus.Size = new System.Drawing.Size(504, 34);
|
||||
lblStatus.TabIndex = 1;
|
||||
lblStatus.Text = "Status:";
|
||||
//
|
||||
// lblHwCaption
|
||||
//
|
||||
lblHwCaption.AutoSize = true;
|
||||
lblHwCaption.Location = new System.Drawing.Point(16, 82);
|
||||
lblHwCaption.Name = "lblHwCaption";
|
||||
lblHwCaption.Size = new System.Drawing.Size(214, 15);
|
||||
lblHwCaption.TabIndex = 2;
|
||||
lblHwCaption.Text = "Hardware-ID dieses Rechners (für die Aktivierung):";
|
||||
//
|
||||
// txtHardwareId
|
||||
//
|
||||
txtHardwareId.Location = new System.Drawing.Point(16, 100);
|
||||
txtHardwareId.Name = "txtHardwareId";
|
||||
txtHardwareId.ReadOnly = true;
|
||||
txtHardwareId.Size = new System.Drawing.Size(400, 23);
|
||||
txtHardwareId.TabIndex = 3;
|
||||
//
|
||||
// btnCopyHwId
|
||||
//
|
||||
btnCopyHwId.Location = new System.Drawing.Point(424, 99);
|
||||
btnCopyHwId.Name = "btnCopyHwId";
|
||||
btnCopyHwId.Size = new System.Drawing.Size(96, 25);
|
||||
btnCopyHwId.TabIndex = 4;
|
||||
btnCopyHwId.Text = "Kopieren";
|
||||
btnCopyHwId.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// lblKeyCaption
|
||||
//
|
||||
lblKeyCaption.AutoSize = true;
|
||||
lblKeyCaption.Location = new System.Drawing.Point(16, 136);
|
||||
lblKeyCaption.Name = "lblKeyCaption";
|
||||
lblKeyCaption.Size = new System.Drawing.Size(96, 15);
|
||||
lblKeyCaption.TabIndex = 5;
|
||||
lblKeyCaption.Text = "Lizenzschlüssel:";
|
||||
//
|
||||
// txtKey
|
||||
//
|
||||
txtKey.CharacterCasing = System.Windows.Forms.CharacterCasing.Upper;
|
||||
txtKey.Location = new System.Drawing.Point(16, 154);
|
||||
txtKey.Name = "txtKey";
|
||||
txtKey.Size = new System.Drawing.Size(400, 23);
|
||||
txtKey.TabIndex = 6;
|
||||
//
|
||||
// btnValidate
|
||||
//
|
||||
btnValidate.Location = new System.Drawing.Point(424, 153);
|
||||
btnValidate.Name = "btnValidate";
|
||||
btnValidate.Size = new System.Drawing.Size(96, 25);
|
||||
btnValidate.TabIndex = 7;
|
||||
btnValidate.Text = "Validieren";
|
||||
btnValidate.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// lblResult
|
||||
//
|
||||
lblResult.Location = new System.Drawing.Point(16, 190);
|
||||
lblResult.Name = "lblResult";
|
||||
lblResult.Size = new System.Drawing.Size(504, 40);
|
||||
lblResult.TabIndex = 8;
|
||||
//
|
||||
// btnLimited
|
||||
//
|
||||
btnLimited.Location = new System.Drawing.Point(16, 236);
|
||||
btnLimited.Name = "btnLimited";
|
||||
btnLimited.Size = new System.Drawing.Size(230, 27);
|
||||
btnLimited.TabIndex = 9;
|
||||
btnLimited.Text = "Ohne Lizenz starten (eingeschränkt)";
|
||||
btnLimited.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// LicenseDialog
|
||||
//
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
ClientSize = new System.Drawing.Size(536, 278);
|
||||
Controls.Add(btnLimited);
|
||||
Controls.Add(lblResult);
|
||||
Controls.Add(btnValidate);
|
||||
Controls.Add(txtKey);
|
||||
Controls.Add(lblKeyCaption);
|
||||
Controls.Add(btnCopyHwId);
|
||||
Controls.Add(txtHardwareId);
|
||||
Controls.Add(lblHwCaption);
|
||||
Controls.Add(lblStatus);
|
||||
Controls.Add(lblHeadline);
|
||||
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
Name = "LicenseDialog";
|
||||
StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
Text = "PolyTrader – Lizenz";
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label lblHeadline;
|
||||
private System.Windows.Forms.Label lblStatus;
|
||||
private System.Windows.Forms.Label lblHwCaption;
|
||||
private System.Windows.Forms.TextBox txtHardwareId;
|
||||
private System.Windows.Forms.Button btnCopyHwId;
|
||||
private System.Windows.Forms.Label lblKeyCaption;
|
||||
private System.Windows.Forms.TextBox txtKey;
|
||||
private System.Windows.Forms.Button btnValidate;
|
||||
private System.Windows.Forms.Label lblResult;
|
||||
private System.Windows.Forms.Button btnLimited;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using LicenseLabrador.Client;
|
||||
using PolyTraderSharp.Licensing;
|
||||
|
||||
namespace PolyTraderSharp.Ui
|
||||
{
|
||||
/// <summary>
|
||||
/// Modaler Lizenzdialog beim Programmstart, wenn keine gültige Lizenz vorliegt. Zeigt die
|
||||
/// Hardware-ID (kopierbar, für die Aktivierung im Lizenz-Admin), nimmt einen Lizenzschlüssel
|
||||
/// entgegen und validiert ihn gegen den Lizenzserver. Bei Erfolg werden <see cref="ValidatedKey"/>
|
||||
/// und <see cref="ValidatedResult"/> gesetzt; der Aufrufer (LicenseGate) speichert den Schlüssel.
|
||||
/// Ohne gültige Lizenz kann der Nutzer eingeschränkt (nur Core-Shell) weiterstarten.
|
||||
/// </summary>
|
||||
public partial class LicenseDialog : Form
|
||||
{
|
||||
private readonly LicenseClient _client;
|
||||
|
||||
/// <summary>Nur gesetzt, wenn im Dialog erfolgreich validiert wurde.</summary>
|
||||
public LicenseResult? ValidatedResult { get; private set; }
|
||||
|
||||
/// <summary>Der erfolgreich validierte Schlüssel (für die verschlüsselte Ablage).</summary>
|
||||
public string? ValidatedKey { get; private set; }
|
||||
|
||||
/// <param name="startupContext">
|
||||
/// true = Aufruf beim Programmstart (keine gültige Lizenz): der Abbruch-Button bietet
|
||||
/// „eingeschränkt starten". false = Aufruf aus den Einstellungen (App läuft bereits): der
|
||||
/// Button heißt „Schließen".
|
||||
/// </param>
|
||||
public LicenseDialog(LicenseClient client, string prefillKey, LicenseResult? initialResult, bool startupContext = true)
|
||||
{
|
||||
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
InitializeComponent();
|
||||
|
||||
txtHardwareId.Text = SafeHardwareId(client);
|
||||
txtKey.Text = prefillKey ?? string.Empty;
|
||||
|
||||
if (initialResult != null)
|
||||
{
|
||||
lblStatus.Text = "Status: " + LicenseGate.Describe(initialResult) +
|
||||
(string.IsNullOrEmpty(initialResult.Message) ? string.Empty : $" – {initialResult.Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
lblStatus.Text = "Status: noch nicht geprüft – bitte „Validieren“ klicken.";
|
||||
}
|
||||
|
||||
if (startupContext)
|
||||
{
|
||||
lblHeadline.Text = "Für PolyTrader wird eine gültige Lizenz benötigt.";
|
||||
btnLimited.Text = "Ohne Lizenz starten (eingeschränkt)";
|
||||
}
|
||||
else
|
||||
{
|
||||
lblHeadline.Text = "Lizenz prüfen / hinterlegen";
|
||||
btnLimited.Text = "Schließen";
|
||||
}
|
||||
|
||||
btnValidate.Click += (_, _) => Validate();
|
||||
btnCopyHwId.Click += (_, _) => CopyHardwareId();
|
||||
btnLimited.Click += (_, _) => { DialogResult = DialogResult.Cancel; Close(); };
|
||||
AcceptButton = btnValidate;
|
||||
}
|
||||
|
||||
private static string SafeHardwareId(LicenseClient client)
|
||||
{
|
||||
try { return client.GetHardwareId(); }
|
||||
catch { return "(nicht ermittelbar)"; }
|
||||
}
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
string key = txtKey.Text.Trim();
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
lblResult.ForeColor = Color.Firebrick;
|
||||
lblResult.Text = "Bitte einen Lizenzschlüssel eingeben.";
|
||||
return;
|
||||
}
|
||||
|
||||
btnValidate.Enabled = false;
|
||||
Cursor = Cursors.WaitCursor;
|
||||
lblResult.ForeColor = SystemColors.ControlText;
|
||||
lblResult.Text = "Prüfe Lizenz …";
|
||||
lblResult.Refresh();
|
||||
|
||||
try
|
||||
{
|
||||
// Blockierend: das SDK nutzt durchgängig ConfigureAwait(false) → kein Deadlock auf dem
|
||||
// UI-Thread; der kurze Freeze (max. HTTP-Timeout) ist im modalen Startdialog vertretbar
|
||||
// und vermeidet jede Abhängigkeit vom WinForms-SynchronizationContext vor Application.Run.
|
||||
LicenseResult result = _client.ValidateAsync(key).GetAwaiter().GetResult();
|
||||
|
||||
if (result.IsUsable)
|
||||
{
|
||||
ValidatedResult = result;
|
||||
ValidatedKey = key;
|
||||
lblResult.ForeColor = Color.Green;
|
||||
lblResult.Text = "Lizenz gültig – PolyTrader startet.";
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
lblResult.ForeColor = Color.Firebrick;
|
||||
string extra = string.IsNullOrEmpty(result.Message) ? string.Empty : $" ({result.Message})";
|
||||
lblResult.Text = "Nicht gültig: " + LicenseGate.Describe(result) + extra;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lblResult.ForeColor = Color.Firebrick;
|
||||
lblResult.Text = "Fehler bei der Prüfung: " + ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Cursor = Cursors.Default;
|
||||
btnValidate.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyHardwareId()
|
||||
{
|
||||
try { Clipboard.SetText(txtHardwareId.Text); }
|
||||
catch { /* Zwischenablage kann kurzzeitig belegt sein – kein harter Fehler */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+31
-1
@@ -26,6 +26,9 @@ namespace PolyTraderSharp.Ui.Views
|
||||
btn_loadsettings = new ToolStripButton();
|
||||
btnGenMasterKey = new ToolStripButton();
|
||||
btnSetOpenRouterKey = new ToolStripButton();
|
||||
btnSetWatchdogToken = new ToolStripButton();
|
||||
btnTestWatchdog = new ToolStripButton();
|
||||
btnSetLicenseKey = new ToolStripButton();
|
||||
tabPage2 = new TabPage();
|
||||
pgAccount = new PropertyGrid();
|
||||
dgvAccounts = new DataGridView();
|
||||
@@ -74,7 +77,7 @@ namespace PolyTraderSharp.Ui.Views
|
||||
// toolStrip2
|
||||
//
|
||||
toolStrip2.ImageScalingSize = new Size(24, 24);
|
||||
toolStrip2.Items.AddRange(new ToolStripItem[] { btn_save, btn_loadsettings, btnGenMasterKey, btnSetOpenRouterKey });
|
||||
toolStrip2.Items.AddRange(new ToolStripItem[] { btn_save, btn_loadsettings, btnGenMasterKey, btnSetOpenRouterKey, btnSetWatchdogToken, btnTestWatchdog, btnSetLicenseKey });
|
||||
toolStrip2.Location = new Point(3, 3);
|
||||
toolStrip2.Name = "toolStrip2";
|
||||
toolStrip2.Size = new Size(1171, 34);
|
||||
@@ -112,6 +115,30 @@ namespace PolyTraderSharp.Ui.Views
|
||||
btnSetOpenRouterKey.Size = new Size(220, 29);
|
||||
btnSetOpenRouterKey.Text = "OpenRouter-Key setzen …";
|
||||
btnSetOpenRouterKey.ToolTipText = "Speichert den OpenRouter-API-Key für den Supervisor (gitignorierte Datei openrouter.key).";
|
||||
//
|
||||
// btnSetWatchdogToken
|
||||
//
|
||||
btnSetWatchdogToken.DisplayStyle = ToolStripItemDisplayStyle.Text;
|
||||
btnSetWatchdogToken.Name = "btnSetWatchdogToken";
|
||||
btnSetWatchdogToken.Size = new Size(220, 29);
|
||||
btnSetWatchdogToken.Text = "Watchdog-Token setzen …";
|
||||
btnSetWatchdogToken.ToolTipText = "Speichert den Watchdog-Agent-Token (maskierte Eingabe, bei gesetztem Master-Key verschlüsselt).";
|
||||
//
|
||||
// btnTestWatchdog
|
||||
//
|
||||
btnTestWatchdog.DisplayStyle = ToolStripItemDisplayStyle.Text;
|
||||
btnTestWatchdog.Name = "btnTestWatchdog";
|
||||
btnTestWatchdog.Size = new Size(210, 29);
|
||||
btnTestWatchdog.Text = "Test-Heartbeat senden";
|
||||
btnTestWatchdog.ToolTipText = "Sendet sofort einen Heartbeat an den konfigurierten Watchdog-Server und meldet das Ergebnis.";
|
||||
//
|
||||
// btnSetLicenseKey
|
||||
//
|
||||
btnSetLicenseKey.DisplayStyle = ToolStripItemDisplayStyle.Text;
|
||||
btnSetLicenseKey.Name = "btnSetLicenseKey";
|
||||
btnSetLicenseKey.Size = new Size(210, 29);
|
||||
btnSetLicenseKey.Text = "Lizenz prüfen / setzen …";
|
||||
btnSetLicenseKey.ToolTipText = "Öffnet den Lizenzdialog (Hardware-ID + Server-Validierung). Gültiger Schlüssel wird gespeichert (bei Master-Key verschlüsselt).";
|
||||
//
|
||||
// tabPage2
|
||||
//
|
||||
@@ -207,6 +234,9 @@ namespace PolyTraderSharp.Ui.Views
|
||||
private ToolStripButton btn_loadsettings;
|
||||
private ToolStripButton btnGenMasterKey;
|
||||
private ToolStripButton btnSetOpenRouterKey;
|
||||
private ToolStripButton btnSetWatchdogToken;
|
||||
private ToolStripButton btnTestWatchdog;
|
||||
private ToolStripButton btnSetLicenseKey;
|
||||
private ToolStrip toolStripAccounts;
|
||||
private ToolStripButton btnAccNew;
|
||||
private ToolStripButton btnAccDelete;
|
||||
|
||||
+161
-1
@@ -3,8 +3,10 @@ using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using PolyTrader.Core.Persistence;
|
||||
using PolyTrader.Core.Security;
|
||||
using PolyTraderSharp.Models;
|
||||
using PolyTraderSharp.Services;
|
||||
|
||||
@@ -23,6 +25,7 @@ namespace PolyTraderSharp.Ui.Views
|
||||
private ServerSettings _settings = new();
|
||||
private ThreemaService? _threema;
|
||||
private MullvadVpnService? _vpn;
|
||||
private WatchdogHeartbeatService? _watchdog;
|
||||
private TerminalLogger? _logger;
|
||||
|
||||
private IAccountRepository? _accountRepo;
|
||||
@@ -37,6 +40,9 @@ namespace PolyTraderSharp.Ui.Views
|
||||
btn_loadsettings.Click += (_, _) => Reload();
|
||||
btnGenMasterKey.Click += (_, _) => GenerateMasterKey();
|
||||
btnSetOpenRouterKey.Click += (_, _) => SetOpenRouterKey();
|
||||
btnSetWatchdogToken.Click += (_, _) => SetWatchdogToken();
|
||||
btnTestWatchdog.Click += async (_, _) => await TestWatchdogAsync();
|
||||
btnSetLicenseKey.Click += (_, _) => SetLicenseKey();
|
||||
UpdateMasterKeyButtonState();
|
||||
|
||||
btnAccNew.Click += (_, _) => AddAccount();
|
||||
@@ -52,13 +58,14 @@ namespace PolyTraderSharp.Ui.Views
|
||||
}
|
||||
|
||||
public void Initialize(ThreemaService threema, MullvadVpnService vpn, TerminalLogger logger,
|
||||
IAccountRepository accountRepo, TradingState state)
|
||||
IAccountRepository accountRepo, TradingState state, WatchdogHeartbeatService? watchdog = null)
|
||||
{
|
||||
_threema = threema;
|
||||
_vpn = vpn;
|
||||
_logger = logger;
|
||||
_accountRepo = accountRepo;
|
||||
_state = state;
|
||||
_watchdog = watchdog;
|
||||
|
||||
Reload();
|
||||
LoadAccounts();
|
||||
@@ -79,6 +86,7 @@ namespace PolyTraderSharp.Ui.Views
|
||||
_settings.Save(SettingsPath);
|
||||
_threema?.ReloadSettings();
|
||||
_vpn?.ReloadSettings();
|
||||
_watchdog?.ReloadSettings();
|
||||
_logger?.Info("Server-Einstellungen gespeichert und Services neu geladen.");
|
||||
MessageBox.Show("Server-Einstellungen gespeichert.", "Erfolg",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
@@ -135,6 +143,158 @@ namespace PolyTraderSharp.Ui.Views
|
||||
dgvAccounts.Refresh();
|
||||
}
|
||||
|
||||
// ===== Watchdog =====
|
||||
|
||||
/// <summary>
|
||||
/// Setzt/entfernt den Watchdog-Agent-Token über eine maskierte Eingabe – damit das Secret
|
||||
/// nicht offen im PropertyGrid steht und die server_settings.xml nicht per Hand bearbeitet
|
||||
/// werden muss. Ist ein Master-Key gesetzt, wird der Token verschlüsselt abgelegt.
|
||||
/// Wird sofort gespeichert, damit der Test-Button und der laufende Dienst ihn direkt nutzen.
|
||||
/// </summary>
|
||||
private void SetWatchdogToken()
|
||||
{
|
||||
bool exists = !string.IsNullOrWhiteSpace(_settings.WatchdogToken);
|
||||
string? token = PromptForSecret("Watchdog Agent-Token",
|
||||
"Agent-Token aus dem Watchdog-Admin eingeben (Header X-Watchdog-Key).\n" +
|
||||
"Wird in der gitignorierten server_settings.xml gespeichert (leer = entfernen).\n" +
|
||||
(exists ? $"Aktuell: {_settings.WatchdogTokenStatus}." : "Aktuell ist KEIN Token gesetzt."));
|
||||
if (token == null) return; // Abbruch
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
_settings.WatchdogToken = "";
|
||||
_settings.Save(SettingsPath);
|
||||
_watchdog?.ReloadSettings();
|
||||
propertyGrid.Refresh();
|
||||
_logger?.Info("Watchdog-Agent-Token entfernt.");
|
||||
MessageBox.Show("Watchdog-Token entfernt.", "Watchdog",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
// Protect() gibt ohne Master-Key den Klartext unverändert zurück – kein stiller
|
||||
// Sicherheitsverlust, der Nutzer wird darauf hingewiesen.
|
||||
_settings.WatchdogToken = SecretProtection.Protect(token.Trim());
|
||||
_settings.Save(SettingsPath);
|
||||
_watchdog?.ReloadSettings();
|
||||
propertyGrid.Refresh();
|
||||
|
||||
bool encrypted = SecretProtection.IsEncrypted(_settings.WatchdogToken);
|
||||
_logger?.Info($"Watchdog-Agent-Token gespeichert ({(encrypted ? "verschlüsselt" : "Klartext")}).");
|
||||
MessageBox.Show(
|
||||
"Watchdog-Token gespeichert und sofort aktiv.\n\n" +
|
||||
(encrypted
|
||||
? "Der Token liegt mit dem Master-Key verschlüsselt in server_settings.xml."
|
||||
: "Hinweis: Es ist kein Master-Key gesetzt – der Token liegt im Klartext in der " +
|
||||
"(gitignorierten) server_settings.xml. Mit „Master-Key erzeugen\" kannst du das ändern."),
|
||||
"Watchdog", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Fehler beim Speichern des Watchdog-Tokens: {ex.Message}",
|
||||
"Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sendet einen Heartbeat mit den GESPEICHERTEN Einstellungen und meldet das Ergebnis.
|
||||
/// Ungespeicherte Änderungen im PropertyGrid wirken bewusst nicht – sonst würde ein
|
||||
/// erfolgreicher Test eine Konfiguration bestätigen, die so nicht auf der Platte liegt.
|
||||
/// </summary>
|
||||
private async Task TestWatchdogAsync()
|
||||
{
|
||||
if (_watchdog == null)
|
||||
{
|
||||
MessageBox.Show("Der Watchdog-Dienst ist nicht verfügbar.", "Watchdog",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
btnTestWatchdog.Enabled = false;
|
||||
try
|
||||
{
|
||||
var result = await _watchdog.SendHeartbeatAsync(
|
||||
"ok", "Test-Heartbeat aus dem PolyTrader-Settings-Fenster");
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger?.Info($"✅ Watchdog-Test-Heartbeat erfolgreich gesendet ({result.Detail}).");
|
||||
MessageBox.Show(
|
||||
$"Test-Heartbeat erfolgreich gesendet ({result.Detail}).\n\n" +
|
||||
"Der Monitor sollte im Watchdog-Dashboard jetzt auf 'up' stehen.",
|
||||
"Watchdog", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.Warning($"⚠️ Watchdog-Test-Heartbeat fehlgeschlagen: {result.Detail}");
|
||||
MessageBox.Show(
|
||||
$"Test-Heartbeat fehlgeschlagen:\n\n{result.Detail}\n\n" +
|
||||
"Bitte URL, Agent-Token und Source prüfen und die Einstellungen vorher speichern.",
|
||||
"Watchdog", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
btnTestWatchdog.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Lizenz (LicenseLabrador) =====
|
||||
|
||||
/// <summary>
|
||||
/// Öffnet denselben Lizenzdialog wie der Programmstart (Hardware-ID + Server-Validierung),
|
||||
/// nur im Verwalten-Modus. Ein erfolgreich validierter Schlüssel wird – bei gesetztem
|
||||
/// Master-Key verschlüsselt – in server_settings.xml gespeichert und gilt ab dem nächsten Start.
|
||||
/// </summary>
|
||||
private void SetLicenseKey()
|
||||
{
|
||||
string current = string.Empty;
|
||||
try { current = SecretProtection.Unprotect(_settings.LicenseKey); }
|
||||
catch { /* nicht lesbar → als leer behandeln */ }
|
||||
|
||||
LicenseLabrador.Client.LicenseClient client;
|
||||
try
|
||||
{
|
||||
client = new LicenseLabrador.Client.LicenseClient(PolyTraderSharp.Licensing.LicenseGate.BuildConfig());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Lizenzprüfung nicht verfügbar: {ex.Message}", "Lizenz",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
using var dlg = new LicenseDialog(client, current, initialResult: null, startupContext: false);
|
||||
dlg.ShowDialog(this);
|
||||
|
||||
if (dlg.ValidatedResult == null || !dlg.ValidatedResult.IsUsable || string.IsNullOrWhiteSpace(dlg.ValidatedKey))
|
||||
return; // abgebrochen oder nicht validiert – nichts speichern
|
||||
|
||||
try
|
||||
{
|
||||
_settings.LicenseKey = SecretProtection.Protect(dlg.ValidatedKey!.Trim());
|
||||
_settings.Save(SettingsPath);
|
||||
propertyGrid.Refresh();
|
||||
|
||||
bool encrypted = SecretProtection.IsEncrypted(_settings.LicenseKey);
|
||||
_logger?.Info($"Lizenz validiert und gespeichert ({(encrypted ? "verschlüsselt" : "Klartext")}).");
|
||||
MessageBox.Show(
|
||||
"Lizenz validiert und gespeichert.\n\n" +
|
||||
(encrypted
|
||||
? "Der Schlüssel liegt mit dem Master-Key verschlüsselt in server_settings.xml."
|
||||
: "Hinweis: Es ist kein Master-Key gesetzt – der Schlüssel liegt im Klartext in der " +
|
||||
"(gitignorierten) server_settings.xml."),
|
||||
"Lizenz", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Fehler beim Speichern des Lizenzschlüssels: {ex.Message}",
|
||||
"Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Master-Key (at-rest-Verschlüsselung, F1) =====
|
||||
|
||||
/// <summary>Pfad der Master-Key-Datei – identisch zu Program.cs (App-Ordner, gitignored).</summary>
|
||||
|
||||
Reference in New Issue
Block a user