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:
@@ -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 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user