Dashboard-Performance: API-Endpoints konsolidieren
Backend:
- GET /api/sessions: Git-Status inline integriert (parallel pro Session)
- Neue Hilfsmethoden GetGitStatusSummaryAsync (Counts) und
GetGitStatusDetailedAsync (Dateinamen, für Einzel-Endpoint)
- GetSessionsWithStats gibt typisierte SessionWithStats zurück
- Neue Models: SessionWithStats, GitStatusSummary
- GET /api/agents: Skills inline pro Agent integriert
- SkillService als Parameter in HandleList
- Skills als Array mit slug/name/description
Frontend:
- Dashboard: 1 paralleler Promise.all statt sequentieller Requests
- Vorher: agents → sessions → N×git-status → M×agent-skills → cronjobs → shares
- Nachher: Promise.all([agents, sessions, cronjobs, shares]) (4 Requests)
- Git-Status direkt aus Session-Response, Skills aus Agent-Response
- Session-Page: Agent-Skills aus inline-Daten statt N×apiGetAgentSkills
Reduziert Dashboard-Requests von ~4+N+M auf 4 (fix).
This commit is contained in:
1 parent
0dcc05ebf9
commit
11acfde081
7 files changed
+265
-119
No files matched your search
+159
-42
@@ -3,8 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Gott.Extensions;
|
||||
using Gott.Models;
|
||||
using Gott.Services;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@@ -31,8 +33,64 @@ public static class SessionEndpoint
|
||||
private static async Task HandleList(HttpContext ctx, DatabaseService db)
|
||||
{
|
||||
var userId = ctx.GetUserId();
|
||||
var sessions = await db.GetSessionsWithStats(userId);
|
||||
await ctx.Response.WriteAsJsonAsync(sessions);
|
||||
var user = await db.GetUser(userId);
|
||||
var sessionList = await db.GetSessionsWithStats(userId);
|
||||
|
||||
// Git-Status parallel für alle Sessions mit Workspace laden
|
||||
var gitTasks = new Dictionary<int, Task<GitStatusSummary>>();
|
||||
for (int i = 0; i < sessionList.Count; i++)
|
||||
{
|
||||
var resolvedWs = WorkspaceHelper.ResolveWorkspace(sessionList[i].Session, user);
|
||||
if (!string.IsNullOrEmpty(resolvedWs))
|
||||
gitTasks[i] = GetGitStatusSummaryAsync(resolvedWs);
|
||||
}
|
||||
|
||||
if (gitTasks.Count > 0)
|
||||
await Task.WhenAll(gitTasks.Values);
|
||||
|
||||
// Ergebnisse zusammenführen
|
||||
var results = new List<object>();
|
||||
for (int i = 0; i < sessionList.Count; i++)
|
||||
{
|
||||
var sw = sessionList[i];
|
||||
var s = sw.Session;
|
||||
object gitStatus = null;
|
||||
if (gitTasks.TryGetValue(i, out var task) && task.IsCompletedSuccessfully && task.Result != null)
|
||||
{
|
||||
var gs = task.Result;
|
||||
if (gs.Add > 0 || gs.Edit > 0 || gs.Delete > 0 || gs.Outgoing > 0)
|
||||
gitStatus = new { add = gs.Add, edit = gs.Edit, delete = gs.Delete, outgoing = gs.Outgoing };
|
||||
}
|
||||
results.Add(new
|
||||
{
|
||||
s.SessionId,
|
||||
s.Name,
|
||||
s.Description,
|
||||
s.Image,
|
||||
s.AgentId,
|
||||
s.Workspace,
|
||||
s.Context,
|
||||
s.CustomCommands,
|
||||
s.AvatarScale,
|
||||
s.AvatarOffsetX,
|
||||
s.AvatarOffsetY,
|
||||
s.ReviewAgentId,
|
||||
s.MaxReviewIterations,
|
||||
s.FinalizerAgentId,
|
||||
s.PreparerAgentId,
|
||||
s.IsDeleted,
|
||||
s.CreatedAt,
|
||||
s.UpdatedAt,
|
||||
sw.MessageCount,
|
||||
sw.ArchiveMessageCount,
|
||||
sw.TotalMessageCount,
|
||||
sw.TelegramConfigured,
|
||||
sw.TelegramLinked,
|
||||
GitStatus = gitStatus
|
||||
});
|
||||
}
|
||||
|
||||
await ctx.Response.WriteAsJsonAsync(results);
|
||||
}
|
||||
|
||||
private static async Task HandleGet(HttpContext ctx, DatabaseService db)
|
||||
@@ -396,6 +454,23 @@ public static class SessionEndpoint
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await GetGitStatusDetailedAsync(workingDir);
|
||||
if (result == null)
|
||||
{
|
||||
ctx.Response.StatusCode = 500;
|
||||
await ctx.Response.WriteAsJsonAsync(new { error = "Git-Status konnte nicht ermittelt werden" });
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.Response.WriteAsJsonAsync(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ermittelt eine Git-Status-Zusammenfassung (nur Counts) für die Dashboard-Ansicht.
|
||||
/// Gibt null zurück bei Fehlern (kein Git-Repo, kein git installiert etc.).
|
||||
/// </summary>
|
||||
private static async Task<GitStatusSummary> GetGitStatusSummaryAsync(string workingDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
@@ -410,24 +485,85 @@ public static class SessionEndpoint
|
||||
};
|
||||
|
||||
using var proc = Process.Start(psi);
|
||||
if (proc == null)
|
||||
{
|
||||
ctx.Response.StatusCode = 500;
|
||||
await ctx.Response.WriteAsJsonAsync(new { error = "Git-Prozess konnte nicht gestartet werden" });
|
||||
return;
|
||||
}
|
||||
if (proc == null) return null;
|
||||
|
||||
var stdout = await proc.StandardOutput.ReadToEndAsync();
|
||||
var stderr = await proc.StandardError.ReadToEndAsync();
|
||||
await proc.WaitForExitAsync();
|
||||
if (proc.ExitCode != 0) return null;
|
||||
|
||||
if (proc.ExitCode != 0)
|
||||
int addCount = 0, editCount = 0, deleteCount = 0;
|
||||
var lines = stdout.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
ctx.Response.StatusCode = 500;
|
||||
await ctx.Response.WriteAsJsonAsync(new { error = stderr.Trim() });
|
||||
return;
|
||||
if (line.Length < 4) continue;
|
||||
var x = line[0];
|
||||
var y = line[1];
|
||||
|
||||
if (x == '?' && y == '?') addCount++;
|
||||
else if (x == 'D' || y == 'D') deleteCount++;
|
||||
else if (x == 'A') addCount++;
|
||||
else editCount++;
|
||||
}
|
||||
|
||||
// Outgoing commits
|
||||
int outgoing = 0;
|
||||
try
|
||||
{
|
||||
var outPsi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = "rev-list --count @{u}..HEAD",
|
||||
WorkingDirectory = workingDir,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var outProc = Process.Start(outPsi);
|
||||
if (outProc != null)
|
||||
{
|
||||
var outResult = (await outProc.StandardOutput.ReadToEndAsync()).Trim();
|
||||
await outProc.WaitForExitAsync();
|
||||
if (outProc.ExitCode == 0 && int.TryParse(outResult, out var c))
|
||||
outgoing = c;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return new GitStatusSummary { Add = addCount, Edit = editCount, Delete = deleteCount, Outgoing = outgoing };
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ermittelt den detaillierten Git-Status (mit Dateinamen) für den Einzel-Endpoint.
|
||||
/// </summary>
|
||||
private static async Task<object> GetGitStatusDetailedAsync(string workingDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = "status --porcelain",
|
||||
WorkingDirectory = workingDir,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var proc = Process.Start(psi);
|
||||
if (proc == null) return null;
|
||||
|
||||
var stdout = await proc.StandardOutput.ReadToEndAsync();
|
||||
await proc.WaitForExitAsync();
|
||||
if (proc.ExitCode != 0) return null;
|
||||
|
||||
var add = new List<string>();
|
||||
var edit = new List<string>();
|
||||
var delete = new List<string>();
|
||||
@@ -436,35 +572,20 @@ public static class SessionEndpoint
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Length < 4) continue;
|
||||
|
||||
var x = line[0]; // index status
|
||||
var y = line[1]; // working tree status
|
||||
var x = line[0];
|
||||
var y = line[1];
|
||||
var file = line.Substring(3);
|
||||
|
||||
// Bei Renames: "R old -> new" => nur den neuen Namen nehmen
|
||||
var arrowIdx = file.IndexOf(" -> ", StringComparison.Ordinal);
|
||||
if (arrowIdx >= 0)
|
||||
file = file.Substring(arrowIdx + 4);
|
||||
|
||||
if (x == '?' && y == '?')
|
||||
{
|
||||
add.Add(file);
|
||||
}
|
||||
else if (x == 'D' || y == 'D')
|
||||
{
|
||||
delete.Add(file);
|
||||
}
|
||||
else if (x == 'A')
|
||||
{
|
||||
add.Add(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
edit.Add(file);
|
||||
}
|
||||
if (x == '?' && y == '?') add.Add(file);
|
||||
else if (x == 'D' || y == 'D') delete.Add(file);
|
||||
else if (x == 'A') add.Add(file);
|
||||
else edit.Add(file);
|
||||
}
|
||||
|
||||
// Outgoing commits zählen (nur wenn Remote existiert)
|
||||
int outgoing = 0;
|
||||
try
|
||||
{
|
||||
@@ -488,17 +609,13 @@ public static class SessionEndpoint
|
||||
outgoing = count;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Kein Remote oder kein Upstream – outgoing bleibt 0
|
||||
}
|
||||
catch { }
|
||||
|
||||
await ctx.Response.WriteAsJsonAsync(new { add, edit, delete, outgoing });
|
||||
return new { add, edit, delete, outgoing };
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
ctx.Response.StatusCode = 500;
|
||||
await ctx.Response.WriteAsJsonAsync(new { error = ex.Message });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user