feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar
This commit is contained in:
+53
-71
@@ -4,6 +4,7 @@ using System.Text.Json;
|
||||
using ClawdDotNet.Core.Config;
|
||||
using ClawdDotNet.Core.Engine;
|
||||
using ClawdDotNet.Core.Scheduling;
|
||||
using ClawdDotNet.Core.Storage;
|
||||
using ClawdDotNet.Core.Tools;
|
||||
using ClawdDotNet.Models;
|
||||
using ClawdDotNet.Services;
|
||||
@@ -25,8 +26,24 @@ public partial class frm_main : Form
|
||||
private readonly ToolRegistry _toolRegistry;
|
||||
private readonly InstanceDirectoryManager _dirManager;
|
||||
private readonly AgentEngine? _agentEngine;
|
||||
private readonly AgentScheduler? _agentScheduler;
|
||||
private readonly ToolJobScheduler? _toolJobScheduler;
|
||||
|
||||
/// <summary>
|
||||
/// Der einzige periodische Treiber (A1). Ersetzt die früheren AgentScheduler/
|
||||
/// ToolJobScheduler — geplante Läufe und Tool-Job-Polls sind jetzt Tasks.
|
||||
/// </summary>
|
||||
private readonly ClawdDotNet.Core.Tasks.TaskScanner? _taskScanner;
|
||||
|
||||
/// <summary>
|
||||
/// A2: Freigabe-/Ablehnungs-Dienst. Die Review-Oberfläche (Liste offener Vorschläge,
|
||||
/// Freigeben/Ablehnen) ruft <see cref="ClawdDotNet.Core.Staging.StagingService"/> auf —
|
||||
/// die Anbindung ist die verbleibende Integration; die Dienst-API steht bereit.
|
||||
/// </summary>
|
||||
private readonly ClawdDotNet.Core.Staging.StagingService? _stagingService;
|
||||
|
||||
// Der Lizenz-Teil ist mit der Deploymentcenter-Umstellung aus dieser Fassung
|
||||
// verschwunden: Startprüfung, laufende Nachprüfung und Notausschalter sitzen jetzt
|
||||
// in ClawdDotNet.App (LicenseGate, LicenseWatch) und in der Avalonia-Oberfläche.
|
||||
// Diese Datei ist nur noch Vorlage für die Portierung — siehe ClawdDotNet.slnx.
|
||||
|
||||
// ─── Services ───
|
||||
private LiveLogViewerService? _logViewer;
|
||||
@@ -51,6 +68,9 @@ public partial class frm_main : Form
|
||||
private readonly Dictionary<string, DateTime> _jobLastRunTimes = new();
|
||||
private bool _suppressAgentSelectionChanged;
|
||||
|
||||
/// <summary>Setzt die Beenden-Rückfrage außer Kraft (Lizenz-Sperre/-Deaktivierung).</summary>
|
||||
private bool _forceClose;
|
||||
|
||||
public frm_main(
|
||||
SettingsManager settingsManager,
|
||||
InstanceConfig instanceConfig,
|
||||
@@ -60,8 +80,8 @@ public partial class frm_main : Form
|
||||
ToolRegistry toolRegistry,
|
||||
InstanceDirectoryManager dirManager,
|
||||
AgentEngine? agentEngine,
|
||||
AgentScheduler? agentScheduler,
|
||||
ToolJobScheduler? toolJobScheduler = null)
|
||||
ClawdDotNet.Core.Tasks.TaskScanner? taskScanner = null,
|
||||
ClawdDotNet.Core.Staging.StagingService? stagingService = null)
|
||||
{
|
||||
_settingsManager = settingsManager;
|
||||
_instanceConfig = instanceConfig;
|
||||
@@ -72,8 +92,8 @@ public partial class frm_main : Form
|
||||
_toolRegistry = toolRegistry;
|
||||
_dirManager = dirManager;
|
||||
_agentEngine = agentEngine;
|
||||
_agentScheduler = agentScheduler;
|
||||
_toolJobScheduler = toolJobScheduler;
|
||||
_taskScanner = taskScanner;
|
||||
_stagingService = stagingService;
|
||||
_instanceSettingsVm = new InstanceSettingsViewModel(_instanceConfig);
|
||||
|
||||
InitializeComponent();
|
||||
@@ -580,8 +600,6 @@ public partial class frm_main : Form
|
||||
|
||||
foreach (var agent in _instanceConfig.Agents)
|
||||
{
|
||||
var lastResult = _agentScheduler?.GetLastResult(agent.AgentId);
|
||||
|
||||
_agentListEntries.Add(new AgentListDisplayEntry
|
||||
{
|
||||
AgentId = agent.AgentId,
|
||||
@@ -590,7 +608,7 @@ public partial class frm_main : Form
|
||||
ToolCount = agent.Tools.Count,
|
||||
HasIdentity = !string.IsNullOrWhiteSpace(agent.Identity) ? "Ja" : "—",
|
||||
HasSoul = !string.IsNullOrWhiteSpace(agent.Soul) ? "Ja" : "—",
|
||||
Status = lastResult?.Status.ToString() ?? "Bereit"
|
||||
Status = _agentEngine?.IsRunning(agent.AgentId) == true ? "Läuft" : "Bereit"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -752,7 +770,7 @@ public partial class frm_main : Form
|
||||
|
||||
private async void OnRunAgentNowClick(object? sender, EventArgs e)
|
||||
{
|
||||
if (_agentScheduler is null || _agentEngine is null)
|
||||
if (_agentEngine is null)
|
||||
{
|
||||
MessageBox.Show("Kein API-Key konfiguriert – Agenten können nicht ausgeführt werden.",
|
||||
"Hinweis", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
@@ -778,8 +796,8 @@ public partial class frm_main : Form
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource(agentConfig.LoopGuard.Timeout);
|
||||
var result = await _agentScheduler.RunNowAsync(
|
||||
agentConfig, "Manual execution triggered by user.", cts.Token);
|
||||
var result = await _agentEngine.RunAsync(
|
||||
agentConfig, "Manual execution triggered by user.", _instanceConfig.InstanceId, cts.Token);
|
||||
|
||||
RefreshAgentList();
|
||||
|
||||
@@ -925,52 +943,14 @@ public partial class frm_main : Form
|
||||
dgv_jobhistory.DataSource = _jobHistoryEntries;
|
||||
RefreshJobHistoryGrid();
|
||||
|
||||
// ─── ToolJobScheduler: OnJobTick → History + LastRun ───
|
||||
if (_toolJobScheduler is not null)
|
||||
{
|
||||
_toolJobScheduler.OnJobTick += OnToolJobTick;
|
||||
}
|
||||
// Tool-Job-Ticks laufen jetzt über den Scanner (Tasks); ihre Historie ergibt sich
|
||||
// aus dem Audit-Log/den Receipts (A3), nicht mehr aus einem Scheduler-Event.
|
||||
|
||||
EnsureBuiltInServices();
|
||||
RefreshJobList();
|
||||
RefreshServiceList();
|
||||
}
|
||||
|
||||
private void OnToolJobTick(string agentId, string jobId, ToolJobResult result)
|
||||
{
|
||||
if (IsDisposed || !IsHandleCreated) return;
|
||||
|
||||
BeginInvoke(() =>
|
||||
{
|
||||
// Track LastRun timestamp
|
||||
_jobLastRunTimes[jobId] = DateTime.Now;
|
||||
|
||||
// Add to history
|
||||
var agent = _instanceConfig.Agents.FirstOrDefault(a => a.AgentId == agentId);
|
||||
var jobConfig = agent?.ToolJobs.FirstOrDefault(j => j.JobId == jobId);
|
||||
|
||||
var historyEntry = new JobHistoryEntry
|
||||
{
|
||||
JobName = jobConfig?.JobTypeId ?? jobId,
|
||||
Agent = agent?.DisplayName ?? agentId,
|
||||
Time = DateTime.Now,
|
||||
JobDescription = $"Tool Job: {jobConfig?.ToolName ?? "?"}",
|
||||
Info = result.LogSummary ?? (result.ShouldWakeAgent ? "Wake Agent" : "No Action"),
|
||||
Status = result.ShouldWakeAgent ? "Success" : (result.LogSummary?.Contains("Fehler") == true ? "Error" : "Success")
|
||||
};
|
||||
|
||||
_jobHistoryService?.Add(historyEntry);
|
||||
_jobHistoryEntries.Insert(0, historyEntry);
|
||||
|
||||
// Trim UI list
|
||||
while (_jobHistoryEntries.Count > 200)
|
||||
_jobHistoryEntries.RemoveAt(_jobHistoryEntries.Count - 1);
|
||||
|
||||
// Refresh job list to update LastRun column
|
||||
RefreshJobList();
|
||||
});
|
||||
}
|
||||
|
||||
private void RefreshJobHistoryGrid()
|
||||
{
|
||||
_jobHistoryEntries.Clear();
|
||||
@@ -1123,9 +1103,9 @@ public partial class frm_main : Form
|
||||
|
||||
if (entry.JobType == "Tool Job")
|
||||
{
|
||||
if (_toolJobScheduler is null)
|
||||
if (_taskScanner is null)
|
||||
{
|
||||
MessageBox.Show("Tool-Job-Scheduler ist nicht aktiv.", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
MessageBox.Show("Scanner ist nicht aktiv.", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1136,11 +1116,13 @@ public partial class frm_main : Form
|
||||
btn_runJob.Text = "⏳ Läuft...";
|
||||
try
|
||||
{
|
||||
var result = await _toolJobScheduler.TriggerJobAsync(agent, jobConfig, CancellationToken.None);
|
||||
// Poll-Jobs sind jetzt Tasks (Id: tj-<agent>-<job>); der Scanner führt sie aus.
|
||||
var ran = await _taskScanner.RunTaskNowAsync(
|
||||
$"tj-{agent.AgentId}-{jobConfig.JobId}", CancellationToken.None);
|
||||
_jobLastRunTimes[jobConfig.JobId] = DateTime.Now;
|
||||
RefreshJobList();
|
||||
|
||||
var status = result.ShouldWakeAgent ? "Wake → Agent gestartet" : (result.LogSummary ?? "Keine Aktion");
|
||||
var status = ran ? "Ausgeführt" : "Nicht bereit (läuft evtl. schon oder ist deaktiviert)";
|
||||
|
||||
_jobHistoryService?.Add(new JobHistoryEntry
|
||||
{
|
||||
@@ -1181,9 +1163,9 @@ public partial class frm_main : Form
|
||||
else
|
||||
{
|
||||
// Agent Wakeup Job
|
||||
if (_agentScheduler is null || _agentEngine is null)
|
||||
if (_agentEngine is null)
|
||||
{
|
||||
MessageBox.Show("Agent-Scheduler ist nicht aktiv.", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
MessageBox.Show("Agent-Engine ist nicht aktiv.", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1193,7 +1175,7 @@ public partial class frm_main : Form
|
||||
btn_runJob.Text = "⏳ Läuft...";
|
||||
try
|
||||
{
|
||||
var result = await _agentScheduler.RunNowAsync(agent, taskMessage, CancellationToken.None);
|
||||
var result = await _agentEngine.RunAsync(agent, taskMessage, _instanceConfig.InstanceId, CancellationToken.None);
|
||||
_jobLastRunTimes[$"agent_{agent.AgentId}"] = DateTime.Now;
|
||||
RefreshJobList();
|
||||
|
||||
@@ -1244,8 +1226,6 @@ public partial class frm_main : Form
|
||||
// Agent Wakeup Jobs
|
||||
if (agent.Scheduler is not null)
|
||||
{
|
||||
var lastResult = _agentScheduler?.GetLastResult(agent.AgentId);
|
||||
|
||||
string nextRun = "—";
|
||||
if (!string.IsNullOrWhiteSpace(agent.Scheduler.Cron))
|
||||
{
|
||||
@@ -1263,7 +1243,7 @@ public partial class frm_main : Form
|
||||
|
||||
var agentLastRun = _jobLastRunTimes.TryGetValue($"agent_{agent.AgentId}", out var agentLastTime)
|
||||
? agentLastTime.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
: (lastResult is not null ? "Letzter Lauf bekannt" : "—");
|
||||
: "—";
|
||||
|
||||
_jobEntries.Add(new JobDisplayEntry
|
||||
{
|
||||
@@ -1275,8 +1255,8 @@ public partial class frm_main : Form
|
||||
RunOnStart = agent.Scheduler.RunOnStart,
|
||||
NextRun = nextRun,
|
||||
LastRun = agentLastRun,
|
||||
LastStatus = lastResult?.Status.ToString() ?? "—",
|
||||
Status = _agentScheduler is not null ? "Aktiv" : "Inaktiv"
|
||||
LastStatus = "—",
|
||||
Status = _taskScanner is not null ? "Aktiv" : "Inaktiv"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1298,8 +1278,6 @@ public partial class frm_main : Form
|
||||
}
|
||||
}
|
||||
|
||||
var lastToolResult = _toolJobScheduler?.GetLastResult(toolJob.JobId);
|
||||
|
||||
var toolLastRun = _jobLastRunTimes.TryGetValue(toolJob.JobId, out var lastTime)
|
||||
? lastTime.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
: "—";
|
||||
@@ -1316,7 +1294,7 @@ public partial class frm_main : Form
|
||||
RunOnStart = toolJob.RunOnStart,
|
||||
NextRun = nextRun,
|
||||
LastRun = toolLastRun,
|
||||
LastStatus = lastToolResult?.LogSummary ?? "—",
|
||||
LastStatus = "—",
|
||||
Status = toolJob.Enabled ? "Aktiv" : "Deaktiviert"
|
||||
});
|
||||
}
|
||||
@@ -1516,7 +1494,8 @@ public partial class frm_main : Form
|
||||
|
||||
private void OnFormClosing(object? sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (e.CloseReason == CloseReason.UserClosing)
|
||||
// Erzwungenes Schließen (Lizenz-Sperre/-Deaktivierung) überspringt die Rückfrage.
|
||||
if (e.CloseReason == CloseReason.UserClosing && !_forceClose)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"ClawdDotNet ist für den 24/7-Betrieb ausgelegt.\n\n" +
|
||||
@@ -1568,17 +1547,20 @@ public partial class frm_main : Form
|
||||
|
||||
private void btn_openAgentFolder_Click(object sender, EventArgs e)
|
||||
{
|
||||
Process.Start("explorer.exe", _instancePath + "\\Agents");
|
||||
SystemShell.OpenFolder(Path.Combine(_instancePath, "Agents"));
|
||||
}
|
||||
|
||||
private void btn_showInstanceFolder_Click(object sender, EventArgs e)
|
||||
{
|
||||
Process.Start("explorer.exe", _instancePath);
|
||||
SystemShell.OpenFolder(_instancePath);
|
||||
}
|
||||
|
||||
private void btn_ShowLogFolder2_Click(object sender, EventArgs e)
|
||||
{
|
||||
Process.Start("explorer.exe", Environment.CurrentDirectory + "/Logs");
|
||||
// _logDirectory statt CurrentDirectory + "/Logs": Das Arbeitsverzeichnis ist
|
||||
// nicht zwingend das Programmverzeichnis, und die Instanz kann ein eigenes
|
||||
// Log-Ziel haben — geöffnet werden soll der Ordner, in den auch geschrieben wird.
|
||||
SystemShell.OpenFolder(Path.GetFullPath(_logDirectory));
|
||||
}
|
||||
|
||||
private void dgv_Jobs_CellContentClick(object sender, DataGridViewCellEventArgs e)
|
||||
|
||||
Reference in New Issue
Block a user