Weboberflaeche hinzugefuegt und Fachlogik nach App herausgeloest (#493, #494)

- 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:
Richard
2026-09-07 21:56:02 +02:00
parent cffd6f4b77
commit 1e9cb789c8
64 changed files with 10543 additions and 554 deletions
+75
View File
@@ -0,0 +1,75 @@
using ClawdDotNet.Core.Config;
namespace ClawdDotNet.Web;
/// <summary>
/// Die geprüften Startangaben der Weboberfläche.
///
/// <para>Getrennt von <see cref="InstanceConfig"/>, weil dort Wünsche stehen und hier
/// Entscheidungen: Der Port ist eine Zahl, die Bindung eine von zweien, und ein Token,
/// das nicht taugt, ist gar keins. Was hier nicht durchkommt, kommt gar nicht erst an
/// den Kestrel.</para>
/// </summary>
public sealed record WebUiOptions(string Url, string Token, bool LoopbackOnly)
{
/// <summary>Kürzer als das hier darf ein Token nicht sein.</summary>
public const int MinimumTokenLength = 20;
/// <summary>
/// Prüft die Angaben der Instanz.
///
/// <para>Gibt <c>null</c> zurück, wenn die Weboberfläche abgeschaltet ist — das ist
/// kein Fehler und die Vorbelegung. <paramref name="problem"/> trägt den Grund,
/// wenn sie eingeschaltet ist, aber nicht starten darf.</para>
///
/// <para><b>Die harte Regel steht hier:</b> Bindung an alle Adressen ohne Token wird
/// abgelehnt, nicht bloß bemängelt. Ein Netzwerkdienst, der Agenten mit Datei-,
/// Mail- und FTP-Zugriff steuert, ist nichts, was versehentlich offen stehen darf.
/// Auch auf Loopback gilt Token-Pflicht: Auf einer Maschine, auf der Agenten
/// Browser-Werkzeuge bedienen, ist „localhost" keine Vertrauensgrenze.</para>
/// </summary>
public static WebUiOptions? TryCreate(InstanceConfig instance, out string? problem)
{
problem = null;
if (!instance.WebUiEnabled)
return null;
var loopbackOnly = !string.Equals(
instance.WebUiBinding, "any", StringComparison.OrdinalIgnoreCase);
var port = instance.WebServerPort;
if (port is < 1 or > 65535)
{
problem = $"Der Port {port} ist keiner. Erwartet wird 1 bis 65535.";
return null;
}
var token = instance.WebUiToken?.Trim() ?? "";
if (token.Length == 0)
{
problem = loopbackOnly
? "Die Weboberfläche braucht ein Zugangstoken (webUiToken in der Instanzdatei)."
: "Die Weboberfläche ist auf alle Adressen gebunden und hat kein Zugangstoken. "
+ "So wird sie nicht gestartet.";
return null;
}
if (token.Length < MinimumTokenLength)
{
problem = $"Das Zugangstoken ist zu kurz — mindestens {MinimumTokenLength} Zeichen.";
return null;
}
var host = loopbackOnly ? "127.0.0.1" : "0.0.0.0";
return new WebUiOptions($"http://{host}:{port}", token, loopbackOnly);
}
/// <summary>
/// Die Adresse, die man einem Menschen zeigt. <c>0.0.0.0</c> ist keine, die sich
/// anklicken lässt.
/// </summary>
public string DisplayUrl => LoopbackOnly ? Url : Url.Replace("0.0.0.0", Environment.MachineName);
}