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