- ClawdDotNet.Web: Kestrel im Prozess der Anwendung, Token-Absicherung (TokenGate mit FixedTimeEquals), Endpunkte unter /api, Ereignisstrom unter /events (Server-Sent Events) und fortlaufendes Log-Streaming. - Weboberflaeche (wwwroot/): Vollstaendiges Single-Page-Frontend mit nativem HTML5, CSS3 und ES-Modulen ohne Buildschritt (strikt textContent, #-Routing, sofortiger Login-Sprung bei fehlendem Token, Light/Dark-Theme, lokale Barlow-Schriften). - ClawdDotNet.App: Fachlogik aus den Avalonia-ViewModels in Dienste herausgeloest (AgentAdministration, JobAdministration, ServiceAdministration, RunCoordinator) und ueber InstanceChange synchronisiert. - Tests: ClawdDotNet.Web.Tests und ClawdDotNet.App.Tests hinzugefuegt, alle 684 Tests erfolgreich.
This commit is contained in:
@@ -10,6 +10,7 @@ using ClawdDotNet.App.Settings;
|
||||
using ClawdDotNet.Desktop.Services;
|
||||
using ClawdDotNet.Desktop.ViewModels;
|
||||
using ClawdDotNet.Desktop.Views;
|
||||
using ClawdDotNet.Web;
|
||||
|
||||
namespace ClawdDotNet.Desktop;
|
||||
|
||||
@@ -24,6 +25,9 @@ public partial class App : Application
|
||||
|
||||
private AppHost? _host;
|
||||
|
||||
/// <summary>Die Weboberflaeche, falls sie eingeschaltet ist. Sonst null.</summary>
|
||||
private WebUiHost? _web;
|
||||
|
||||
/// <summary>Vorgewaehlte Instanz aus der Befehlszeile; wird genau einmal verbraucht.</summary>
|
||||
private string? _preselectedInstancePath;
|
||||
|
||||
@@ -44,6 +48,10 @@ public partial class App : Application
|
||||
|
||||
desktop.ShutdownRequested += async (_, _) =>
|
||||
{
|
||||
// Reihenfolge: erst die Weboberflaeche, dann der Aufbau. Andersherum
|
||||
// bediente Kestrel noch Anfragen gegen eine bereits geschlossene
|
||||
// Datenbank.
|
||||
await ShutdownWebAsync();
|
||||
if (_host is not null) await _host.DisposeAsync();
|
||||
};
|
||||
|
||||
@@ -110,6 +118,11 @@ public partial class App : Application
|
||||
HookErrorReporting(_host);
|
||||
HookLicenseWatch(_host, desktop);
|
||||
|
||||
// Nach dem Aufbau und vor dem Fenster: Die Weboberflaeche braucht den fertigen
|
||||
// AppHost, und ein belegter Port darf das Fenster nicht aufhalten — TryStart
|
||||
// gibt in jedem Fehlerfall null zurueck und schreibt den Grund ins Protokoll.
|
||||
_web = WebUiHost.TryStart(_host, _host.LoggerFactory);
|
||||
|
||||
var mainViewModel = new MainWindowViewModel(_host);
|
||||
|
||||
// Der Wechsel gehoert hierher und nicht ins Fenster: Er braucht den Aufbau und
|
||||
@@ -181,7 +194,7 @@ public partial class App : Application
|
||||
/// der Benutzer — eine Anwendung, die sich beim Start selbst beendet, um sich zu
|
||||
/// erneuern, ist genau dann im Weg, wenn man sie braucht.</para>
|
||||
/// </summary>
|
||||
private static async Task OfferUpdateAsync(AppHost host, IClassicDesktopStyleApplicationLifetime desktop)
|
||||
private async Task OfferUpdateAsync(AppHost host, IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (host.Deploymentcenter is null) return;
|
||||
|
||||
@@ -221,12 +234,17 @@ public partial class App : Application
|
||||
/// das tut es aber über <c>Environment.Exit</c> und übergeht damit Schritt 2
|
||||
/// vollständig.</para>
|
||||
/// </summary>
|
||||
private static async Task StartUpdateAsync(
|
||||
private async Task StartUpdateAsync(
|
||||
AppHost host, IClassicDesktopStyleApplicationLifetime desktop, string version)
|
||||
{
|
||||
host.Deploymentcenter!.AnnounceUpdate(version);
|
||||
|
||||
var settings = host.Settings.AppSettings;
|
||||
|
||||
// Vor dem Aufbau: Kestrel darf keine Anfrage mehr gegen eine geschlossene
|
||||
// Datenbank bedienen — und schon gar nicht, waehrend der update-agent
|
||||
// gleich die Dateien darunter austauscht.
|
||||
await ShutdownWebAsync();
|
||||
await host.DisposeAsync();
|
||||
|
||||
var problem = host.Deploymentcenter.StartUpdate(settings, AppHost.AppVersion);
|
||||
@@ -322,6 +340,8 @@ public partial class App : Application
|
||||
if (path is null) return;
|
||||
if (string.Equals(path, _host.InstancePath, StringComparison.OrdinalIgnoreCase)) return;
|
||||
|
||||
await ShutdownWebAsync();
|
||||
|
||||
var executable = Environment.ProcessPath;
|
||||
|
||||
if (executable is null)
|
||||
@@ -353,6 +373,21 @@ public partial class App : Application
|
||||
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Faehrt die Weboberflaeche herunter, falls sie laeuft, und merkt sich, dass sie
|
||||
/// weg ist. Mehrfach aufrufbar: Update, Instanzwechsel und das gewoehnliche
|
||||
/// Beenden nehmen denselben Weg, und der letzte von ihnen soll nicht ins Leere
|
||||
/// greifen.
|
||||
/// </summary>
|
||||
private async Task ShutdownWebAsync()
|
||||
{
|
||||
if (_web is null) return;
|
||||
|
||||
var web = _web;
|
||||
_web = null;
|
||||
await web.DisposeAsync();
|
||||
}
|
||||
|
||||
private static string? ReadInstanceArgument(IReadOnlyList<string>? args)
|
||||
{
|
||||
if (args is null) return null;
|
||||
|
||||
Reference in New Issue
Block a user