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
@@ -28,9 +28,35 @@ public sealed class InstanceConfig
[JsonPropertyName("logDirectory")]
public string LogDirectory { get; set; } = "";
/// <summary>
/// Port der Weboberfläche. Je Instanz und nicht anwendungsweit: Zwei Instanzen auf
/// einer Maschine stritten sich sonst um einen Port.
/// </summary>
[JsonPropertyName("webServerPort")]
public int WebServerPort { get; set; } = 8080;
/// <summary>
/// Ob die Weboberfläche mitläuft. Ausgeschaltet vorbelegt — sie macht aus einer
/// lokalen Anwendung einen Netzwerkdienst, der Agenten mit Datei-, Mail- und
/// FTP-Zugriff steuert. Das schaltet man bewusst ein.
/// </summary>
[JsonPropertyName("webUiEnabled")]
public bool WebUiEnabled { get; set; }
/// <summary>
/// <c>loopback</c> (Vorgabe) oder <c>any</c>. Bei <c>any</c> ist
/// <see cref="WebUiToken"/> Pflicht, sonst startet die Weboberfläche nicht.
/// </summary>
[JsonPropertyName("webUiBinding")]
public string WebUiBinding { get; set; } = "loopback";
/// <summary>
/// Zugangstoken der Weboberfläche. Steht in <c>ConfigSecrets</c> und liegt damit
/// geschützt in der Instanzdatei — wie der OpenRouter-Schlüssel daneben.
/// </summary>
[JsonPropertyName("webUiToken")]
public string WebUiToken { get; set; } = "";
[JsonPropertyName("telegramClient")]
public TelegramClientConfig? TelegramClient { get; set; }
+5
View File
@@ -15,6 +15,11 @@ public sealed record ChatEntry(
public static class ChatSource
{
public const string WebView = "webview";
/// <summary>Die Weboberfläche im Browser — nicht zu verwechseln mit <see cref="WebView"/>,
/// das aus der WinForms-Zeit stammt und den eingebetteten Browser meinte.</summary>
public const string WebUi = "webui";
public const string Telegram = "telegram";
public const string AgentComm = "agentcomm";
public const string Job = "job";
@@ -22,7 +22,7 @@ public static class ConfigSecrets
{
"password", "password2fa", "passwort",
"apikey", "api_key", "xapikey", "openrouterapikey", "apihash", "apisecret",
"token", "accesstoken", "bottoken", "authtoken",
"token", "accesstoken", "bottoken", "authtoken", "webuitoken",
"secret", "clientsecret",
"connectionstring"
};
@@ -39,6 +39,12 @@ public static class ConfigSecrets
{
config.OpenRouterApiKey = transform(config.OpenRouterApiKey) ?? "";
// Das Zugangstoken der Weboberflaeche ist ein Zugangsdatum wie jedes andere.
// Ohne diese Zeile stuende es im Klartext neben dem geschuetzten
// OpenRouter-Schluessel — der Name allein in SecretKeys traegt hier nicht, weil
// InstanceConfig kein freies Woerterbuch ist, sondern feste Felder hat.
config.WebUiToken = transform(config.WebUiToken) ?? "";
if (config.TelegramClient is { } telegram)
{
telegram.ApiHash = transform(telegram.ApiHash) ?? "";