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
+177
View File
@@ -0,0 +1,177 @@
namespace ClawdDotNet.Web.Contracts;
// ─────────────────────────────────────────────────────────────────────────────
// Die Übertragungstypen der Weboberfläche.
//
// Warum es sie gibt, statt die Kerntypen zu serialisieren:
//
// 1. AgentConfig trägt Zugangsdaten. Ein `return agent;` hätte den
// Werkzeug-Konfigurationsbaum samt Passwörtern ins Netz geschickt — die Sorte
// Fehler, die niemandem auffällt, weil alles funktioniert.
// 2. Die Kerntypen ändern sich mit dem Kern. Ein neues Feld in TaskItem wäre sonst
// still eine API-Änderung, auf die sich jemand verlassen hat.
// 3. Die Grenze soll einen Prozesswechsel überstehen (siehe WebUiHost). Über eine
// Prozessgrenze geht nur, was hier steht.
//
// Namen auf Englisch in camelCase wie im übrigen Code; die Beschreibungen auf Deutsch.
// ─────────────────────────────────────────────────────────────────────────────
/// <summary>Antwort auf einen verwaltenden Aufruf — dieselbe Form wie <c>AdminResult</c>.</summary>
public sealed record OutcomeDto(bool Ok, string Message);
/// <summary>Version, Bau und Instanz. Was auf der Info-Seite steht.</summary>
public sealed record InfoDto(
string AppVersion,
string BuildSummary,
string InstanceId,
string InstanceName,
bool EngineActive,
bool SchedulerActive,
bool StagingActive,
bool DeploymentcenterActive);
/// <summary>Erreichbarkeit und Guthaben bei OpenRouter.</summary>
public sealed record StatusDto(
bool ApiReachable,
string StatusText,
string CreditsText,
double? CreditBalance,
double? CreditRemaining);
/// <summary>Ein Agent, ohne alles, was ein Zugangsdatum sein könnte.</summary>
public sealed record AgentDto(
string AgentId,
string DisplayName,
string Model,
string Description,
string PromptCaching,
int MaxToolResultChars,
int MaxSteps,
int MaxContextTokens,
int TimeoutSeconds,
decimal DailyCostUsd,
long DailyTokens,
bool IsRunning,
IReadOnlyList<AgentToolDto> Tools);
/// <summary>
/// Ein Werkzeug am Agenten. <b>Ohne die Einstellungen</b> — die stehen in einem freien
/// Wörterbuch und enthalten regelmäßig Passwörter und Schlüssel. Was einstellbar ist und
/// ob etwas gesetzt ist, sagt <see cref="ToolSettingsDto"/>.
/// </summary>
public sealed record AgentToolDto(string Name, string Description, bool Enabled);
/// <summary>
/// Die Einstellungen eines Werkzeugs, maskiert.
///
/// <para>Ein Wert, dessen Name ein Zugangsdatum bezeichnet, kommt nie heraus. Statt
/// seiner steht in <see cref="Secrets"/>, ob überhaupt etwas gesetzt ist — mehr braucht
/// eine Anzeige nicht, und mehr darf sie nicht wissen.</para>
/// </summary>
public sealed record ToolSettingsDto(
string Name,
IReadOnlyDictionary<string, object?> Values,
IReadOnlyDictionary<string, bool> Secrets);
/// <summary>Eine Zeile im Chatverlauf.</summary>
public sealed record ChatEntryDto(
string Role,
string Content,
DateTime Timestamp,
string? Source);
/// <summary>Was der Browser schickt, wenn jemand etwas in den Chat tippt.</summary>
public sealed record SendMessageDto(string Message);
/// <summary>Ein geplanter Lauf.</summary>
public sealed record JobDto(
string Kind,
string JobId,
string AgentId,
string AgentName,
string ToolName,
string Cron,
string Message,
bool RunOnStart,
DateTime? NextRunUtc,
DateTime? LastRunLocal,
bool CronValid,
bool Enabled);
/// <summary>Was einen geplanten Lauf ausmacht — beim Anlegen wie beim Ändern.</summary>
public sealed record JobInputDto(
string AgentId,
string Kind,
string ToolName = "",
string JobTypeId = "",
string Cron = "",
string TaskMessage = "",
bool RunOnStart = false);
/// <summary>Ein Dienst der Instanz.</summary>
public sealed record ServiceDto(
string ServiceId,
string Name,
string Type,
int Port,
bool Enabled,
bool BuiltIn,
string Description);
/// <summary>Was einen neuen Dienst ausmacht.</summary>
public sealed record ServiceInputDto(string Name, string Type, int Port, string Description = "");
/// <summary>Ein neuer Agent.</summary>
public sealed record NewAgentDto(string AgentId, string DisplayName, string Model, string Description = "");
/// <summary>Eine Aufgabe vom Taskboard.</summary>
public sealed record TaskDto(
string Id,
string Title,
string Status,
string Type,
int Priority,
string Assignee,
bool RequireApproval,
string Acceptance,
IReadOnlyList<string> BlockedBy,
string Body,
DateTime CreatedAt,
DateTime UpdatedAt);
/// <summary>Ein eingefrorener Aufruf, der auf Freigabe wartet.</summary>
public sealed record StagedCallDto(
long Id,
string RunId,
string AgentId,
string Tool,
string? Action,
string Proposal,
string Status,
string ArgumentsJson);
/// <summary>Warum ein Vorschlag abgelehnt wurde.</summary>
public sealed record RejectDto(string Reason);
/// <summary>
/// Ein Lauf mit dem, was er gekostet hat.
///
/// <para><see cref="CostIsKnown"/> ist nicht schmückendes Beiwerk: Ohne geladene
/// Preisliste steht in <see cref="CostUsd"/> eine Null, die „kostenlos" heißen könnte
/// und „unbekannt" heißt. Eine Anzeige, die das nicht unterscheidet, lügt.</para>
/// </summary>
public sealed record UsageDto(
long Id,
string AgentId,
string Model,
int PromptTokens,
int CompletionTokens,
int CachedTokens,
decimal CostUsd,
bool CostIsKnown,
string Status,
int StepCount,
DateTime OccurredAt);
/// <summary>Eine Protokollzeile.</summary>
public sealed record LogLineDto(string Module, string Level, string Text);