WinForms-Host durch die Avalonia-Shell abgeloest

Merge von main: die dort entstandene Deployment-Center-Integration (Lizenz,
Heartbeat mit DB-Health-Check, Fehler-Stream, UpdateService) ist jetzt Teil
des plattformneutralen Kerns. Predictalytics.WinFormsHost ist entfernt.

Nach Predictalytics.Hosting gezogen:
- DcConfig, DcApiClient, DcErrorSink, DcHeartbeatService, DcUpdateService
  unveraendert - sie waren bereits plattformneutral
- DcErrorReporter ohne Application.ThreadException und MessageBox; der
  UI-Thread-Handler liegt jetzt beim Host und ruft ReportUiThreadException
- LicenseGuard/LicenseSession ohne Dialog und ohne WinForms-Timer. Neu:
  TryUseCachedAsync, ActivateAsync, StartPeriodicRevalidation ueber
  PeriodicTimer. Die Unterscheidung transienter Fehler und die Warnung vor
  ablaufender Gnadenfrist sind unveraendert uebernommen.
- Dc-Einstellungen von AppSettings nach PredictalyticsOptions; die
  Watchdog-Einstellungen entfallen
- DcErrorSink im LoggingSetup, Startbanner nutzt DcConfig.AppVersion

BuildInfo.targets wird jetzt von Predictalytics.Hosting importiert.

In der Avalonia-Shell nachgezogen:
- Menue Deployment Center mit Update-Suche und Lizenzstatus
- Einstellungsgruppe Deployment Center statt Watchdog, Update-Kanal als
  ComboBox, Server-URL nur zur Anzeige
- Heartbeat-Snapshot mit SELECT-1-Probe wie in der WinForms-Fassung
- Update-Pruefung still beim Start und interaktiv ueber das Menue, mit
  NotifyStopping vor dem Start des Update-Agenten
- Lizenzfenster wertet IsTransient aus: bei fehlender Serververbindung wird
  nicht behauptet, die Lizenz sei ungueltig
- TextBox.Watermark auf PlaceholderText (in Avalonia 12 veraltet)

Build: 0 Fehler, 8 Warnungen (alle vorbestehend).
Tests: 100 bestanden, 0 Fehler, 1 uebersprungen.
Verifiziert: --license-status meldet gueltig samt Gnadenfrist; die GUI
startet durch, prueft die Lizenz und laeuft gegen den UpdateService.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-08 21:07:13 +02:00
co-authored by Claude Opus 5
35 changed files with 1694 additions and 1578 deletions
@@ -8,8 +8,8 @@ namespace Predictalytics.Hosting;
/// Laufzeit-Konfiguration der Anwendung.
/// <para>
/// Die <see cref="System.ComponentModel"/>-Attribute sind plattformneutral (Teil der
/// Basisbibliothek) und werden vom WinForms-PropertyGrid ausgewertet. Die Avalonia-Shell
/// kann sie fuer generierte Beschriftungen nutzen oder ignorieren.
/// Basisbibliothek). Die Avalonia-Shell nutzt sie fuer Beschriftungen und Hilfetexte
/// der Einstellungsansicht.
/// </para>
/// </summary>
public sealed class PredictalyticsOptions
@@ -47,43 +47,62 @@ public sealed class PredictalyticsOptions
"Beispiel: prox-1|Proxy|http://user:pass@proxy:8080\nip-1|SourceIp|192.168.1.100")]
public string EgressChannelsText { get => _egressChannelsText; set => _egressChannelsText = value ?? ""; }
// ─── Watchdog ───
// ─── Deployment Center ───
[Category("Watchdog")]
[DisplayName("Enabled")]
[Description("Sendet periodische Heartbeats an den externen Watchdog-Server (Dead-Man's-Switch). Benötigt einen API Key.")]
[DefaultValue(true)]
public bool WatchdogEnabled { get; set; } = true;
[Category("Watchdog")]
[Category("Deployment Center")]
[DisplayName("Server URL")]
[Description("Basis-URL des Watchdog-Servers.")]
[DefaultValue("https://watchdog.mhdf.de")]
public string WatchdogUrl { get; set; } = "https://watchdog.mhdf.de";
[Description("Basis-URL des Deployment Centers. Fest einkompiliert — ein einstellbarer Endpoint würde erlauben, die App auf einen gefälschten Lizenz- oder Update-Server zu zeigen.")]
[ReadOnly(true)]
[JsonIgnore]
public string DcServerUrl => DcConfig.BaseUrl;
[Category("Watchdog")]
[DisplayName("API Key")]
[Description("Shared Key oder Agent-Token des Watchdog-Servers (X-Watchdog-Key). Ohne Key werden keine Heartbeats gesendet.")]
[Category("Deployment Center")]
[DisplayName("API Token")]
[Description("Token des Deployment Centers (Authorization: Bearer). Benötigte Rechte: 'watchdog:ping' für Heartbeats, 'bugtracker:report' für das Fehler-Reporting. Ohne Token werden weder Heartbeats noch Fehler gemeldet.")]
[PasswordPropertyText(true)]
public string WatchdogApiKey { get; set; } = "";
public string DcToken { get; set; } = "";
[Category("Watchdog")]
[DisplayName("Source")]
[Category("Deployment Center")]
[DisplayName("Heartbeat aktiv")]
[Description("Sendet periodische Heartbeats an den Watchdog des Deployment Centers (Dead-Man's-Switch). Benötigt ein Token.")]
[DefaultValue(true)]
public bool DcHeartbeatEnabled { get; set; } = true;
[Category("Deployment Center")]
[DisplayName("Monitor Source")]
[Description("Eindeutiger Monitor-Name dieses Dienstes im Watchdog-Dashboard.")]
[DefaultValue("Predictalytics")]
public string WatchdogSource { get; set; } = "Predictalytics";
public string DcSource { get; set; } = "Predictalytics";
[Category("Watchdog")]
[DisplayName("Instance")]
[Category("Deployment Center")]
[DisplayName("Monitor Instance")]
[Description("Instanz-Kennung, falls mehrere Predictalytics-Instanzen laufen.")]
[DefaultValue("default")]
public string WatchdogInstance { get; set; } = "default";
public string DcInstance { get; set; } = "default";
[Category("Watchdog")]
[DisplayName("Interval (Sekunden)")]
[Description("Sende-Takt der Heartbeats. Der Watchdog alarmiert, wenn ~1,5× dieses Intervall + 30 s ohne Heartbeat vergehen.")]
[Category("Deployment Center")]
[DisplayName("Heartbeat-Intervall (Sekunden)")]
[Description("Sende-Takt der Heartbeats. Der Evaluator stuft nach dem Doppelten auf 'warning' und nach dem Vierfachen auf 'down'.")]
[DefaultValue(60)]
public int WatchdogIntervalSeconds { get; set; } = 60;
public int DcHeartbeatIntervalSeconds { get; set; } = 60;
[Category("Deployment Center")]
[DisplayName("Fehler melden")]
[Description("Meldet Laufzeitfehler (Error/Fatal) an den Fehler-Stream des Deployment Centers. Gleiche Fehler werden dort gruppiert und hochgezählt.")]
[DefaultValue(true)]
public bool DcErrorReportingEnabled { get; set; } = true;
[Category("Deployment Center")]
[DisplayName("Update-Prüfung beim Start")]
[Description("Prüft beim Start, ob im gewählten Kanal ein neueres Release vorliegt. Installiert wird nichts automatisch.")]
[DefaultValue(true)]
public bool DcUpdateCheckEnabled { get; set; } = true;
[Category("Deployment Center")]
[DisplayName("Update-Kanal")]
[Description("prod, beta oder dev.")]
[DefaultValue("prod")]
public string DcUpdateChannel { get; set; } = "prod";
// ─── Database ───