Neues Projekt Predictalytics.Shell (Avalonia 12.1.1, net10.0) als plattformuebergreifender Ersatz fuer den WinForms-Host. Setzt auf dem in Phase 2 extrahierten Predictalytics.Hosting auf. Oberflaeche: - MainWindow mit Menue, Werkzeugleiste, Terminal-Tab, Einstellungen-Tab und Statusleiste. Die feste Fenstergroesse von 1886x1088 ist aufgeloest. - Terminal als ListBox ueber ObservableCollection mit Einfaerbung nach Loglevel. Ringpuffer statt des bisherigen kompletten Leerens bei 500 Zeilen. - Einstellungsansicht handgeschrieben als Ersatz fuer den PropertyGrid, den es in Avalonia nicht gibt: vier Gruppen, Passwortfelder, mehrzeiliges Textfeld fuer die Egress-Kanaele, ComboBox fuer den SSL-Modus. Gespeichert wird explizit statt bei jeder Einzeloperation. - LicenseWindow ersetzt den WinForms-LicenseDialog. - Dialogs.cs als schlanker Ersatz fuer MessageBox.Show, ohne Drittanbieter. Headless-Modus (--headless) im selben Binary: fuer Linux-Server ohne Desktop-Session. Die Argumentauswertung steht vor jeder Avalonia- Initialisierung, sonst stirbt der Prozess ohne X11 bevor der Schalter greift. Lizenz ueber PREDICTALYTICS_LICENSE_KEY statt Dialog, SIGTERM und SIGINT ueber PosixSignalRegistration fuer sauberes systemd-Stop. Exit-Codes: 0 planmaessig, 2 keine nutzbare Lizenz, 3 Fehler. Plattformdetails: Schrift-Fallbackkette fuers Terminal, Avalonia.Fonts.Inter als mitgelieferte UI-Schrift, UTF-8 fuer die Windows-Konsole im Headless-Modus. Die Avalonia-Basisklasse wird als global::Avalonia.Application angegeben: innerhalb von Predictalytics.* loest der kurze Name auf den eigenen Namespace Predictalytics.Application auf. Build: 0 Fehler, 0 Warnungen. Tests: 100 bestanden. Verifiziert: --headless laeuft bis zur Lizenzschranke und beendet sich mit Exit-Code 2; GUI startet und zeigt das Lizenzfenster. Das Hauptfenster konnte nicht geprueft werden, weil die hinterlegte Lizenz abgelaufen ist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
3.2 KiB
C#
93 lines
3.2 KiB
C#
using Avalonia.Controls;
|
|
using Avalonia.Controls.ApplicationLifetimes;
|
|
using Avalonia.Markup.Xaml;
|
|
using Avalonia.Threading;
|
|
using Predictalytics.Hosting;
|
|
using Predictalytics.Shell.ViewModels;
|
|
using Predictalytics.Shell.Views;
|
|
using Serilog;
|
|
|
|
namespace Predictalytics.Shell;
|
|
|
|
/// <summary>
|
|
/// Avalonia-Anwendungsobjekt.
|
|
/// <para>
|
|
/// Die Basisklasse wird voll qualifiziert angegeben: innerhalb von
|
|
/// <c>Predictalytics.*</c> loest der kurze Name <c>Application</c> auf den
|
|
/// Namespace <c>Predictalytics.Application</c> auf, nicht auf den Avalonia-Typ.
|
|
/// </para>
|
|
/// </summary>
|
|
public partial class App : global::Avalonia.Application
|
|
{
|
|
private IDisposable? _licenseWatch;
|
|
|
|
public override void Initialize() => AvaloniaXamlLoader.Load(this);
|
|
|
|
public override void OnFrameworkInitializationCompleted()
|
|
{
|
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
{
|
|
// Das Lizenzfenster darf beim Schliessen nicht die Anwendung beenden —
|
|
// danach soll ja erst das Hauptfenster kommen.
|
|
desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
|
desktop.Exit += (_, _) => _licenseWatch?.Dispose();
|
|
|
|
// Nicht awaiten: OnFrameworkInitializationCompleted muss zurueckkehren,
|
|
// damit die Nachrichtenschleife anlaufen kann.
|
|
Dispatcher.UIThread.Post(async () => await StartupAsync(desktop));
|
|
}
|
|
|
|
base.OnFrameworkInitializationCompleted();
|
|
}
|
|
|
|
private async Task StartupAsync(IClassicDesktopStyleApplicationLifetime desktop)
|
|
{
|
|
// ─── Lizenzschranke: ohne nutzbare Lizenz keine Anwendung ───
|
|
var client = LicenseGuard.CreateClient();
|
|
var result = await LicenseGuard.RevalidateAsync(client);
|
|
|
|
if (!LicenseGuard.IsUsable(client, result))
|
|
{
|
|
var licenseWindow = new LicenseWindow(client, result);
|
|
desktop.MainWindow = licenseWindow;
|
|
licenseWindow.Show();
|
|
|
|
if (!await licenseWindow.Completion)
|
|
{
|
|
desktop.Shutdown();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ─── Hauptfenster ───
|
|
var viewModel = new MainWindowViewModel();
|
|
var window = new MainWindow(viewModel);
|
|
|
|
// Serilog-Aufbau liegt in Predictalytics.Hosting; hier kommt nur der
|
|
// Terminal-Sink dazu, der selbst auf den UI-Thread marshallt.
|
|
LoggingSetup.Configure(viewModel.AppendLog);
|
|
LoggingSetup.LogStartupBanner();
|
|
|
|
desktop.MainWindow = window;
|
|
desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
|
|
window.Show();
|
|
|
|
viewModel.Start();
|
|
|
|
// Waehrend des Betriebs alle 12 h nachpruefen (Widerruf, Ablauf, Offline-Frist).
|
|
_licenseWatch = LicenseGuard.StartPeriodicRevalidation(client, unusable =>
|
|
Dispatcher.UIThread.Post(async () =>
|
|
{
|
|
await Dialogs.ShowErrorAsync(window, "Lizenzfehler",
|
|
$"Die Lizenz ist nicht mehr gültig ({unusable.State}):\n{unusable.Message}\n\nPredictalytics wird beendet.");
|
|
desktop.Shutdown();
|
|
}));
|
|
|
|
desktop.Exit += (_, _) =>
|
|
{
|
|
Log.Information("Application shutting down.");
|
|
Log.CloseAndFlush();
|
|
};
|
|
}
|
|
}
|