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:
Meleki committed 2026-03-07 19:40:26 +01:00
1 parent 0dcc05ebf9
commit 11acfde081
7 files changed
+265 -119

No files matched your search

+35 -49
View File
@@ -35,12 +35,13 @@ registerPage('dashboard', {
// Telegram-Config parallel pro Session laden (sessions werden gleich geladen)
const agents = await apiGetAgents();
const [agents, sessions, cronjobs, shares] = await Promise.all([
apiGetAgents(), apiGetSessions(), apiGetCronjobs(), apiListShares()
]);
const agentMap = {};
for (const a of agents) agentMap[a.agentId] = a;
// Sessions
const sessions = await apiGetSessions();
const grid = document.getElementById('sessionCards');
grid.innerHTML = '';
@@ -48,6 +49,37 @@ registerPage('dashboard', {
const agent = s.agentId ? agentMap[s.agentId] : null;
const card = buildSessionCard(s, agent);
// Git-Status aus der Session-Response rendern (inline vom Backend)
if (s.gitStatus) {
const gs = s.gitStatus;
const metaEl = card.querySelector('.card-meta');
if (metaEl) {
const parts = [];
if (gs.add > 0) {
const tip = `${gs.add} ${gs.add === 1 ? 'Datei' : 'Dateien'} hinzugefügt`;
parts.push(`<span class="git-badge git-add" title="${esc(tip)}">+${gs.add}</span>`);
}
if (gs.edit > 0) {
const tip = `${gs.edit} ${gs.edit === 1 ? 'Datei' : 'Dateien'} geändert`;
parts.push(`<span class="git-badge git-edit" title="${esc(tip)}">~${gs.edit}</span>`);
}
if (gs.delete > 0) {
const tip = `${gs.delete} ${gs.delete === 1 ? 'Datei' : 'Dateien'} gelöscht`;
parts.push(`<span class="git-badge git-delete" title="${esc(tip)}">-${gs.delete}</span>`);
}
if (gs.outgoing > 0) {
const tip = `${gs.outgoing} ${gs.outgoing === 1 ? 'Commit muss' : 'Commits müssen'} noch gepusht werden`;
parts.push(`<span class="git-badge git-outgoing" title="${esc(tip)}"><i class="fa fa-arrow-up"></i> ${gs.outgoing}</span>`);
}
if (parts.length) {
const gitEl = document.createElement('span');
gitEl.className = 'card-meta-item card-git-status';
gitEl.innerHTML = parts.join(' ');
metaEl.appendChild(gitEl);
}
}
}
// Menü zum title-row hinzufügen
const titleRow = card.querySelector('.card-title-row');
titleRow.insertAdjacentHTML('beforeend', `<div class="card-menu">
@@ -105,44 +137,6 @@ registerPage('dashboard', {
navigateTo('session', { id: s.sessionId });
});
grid.appendChild(card);
// Git-Status asynchron laden
if (s.workspace) {
apiGitStatus(s.sessionId).then(status => {
if (!status || status.error) return;
const addCount = (status.add || []).length;
const editCount = (status.edit || []).length;
const deleteCount = (status.delete || []).length;
const outgoing = status.outgoing || 0;
if (addCount + editCount + deleteCount + outgoing === 0) return;
const metaEl = card.querySelector('.card-meta');
if (!metaEl) return;
const parts = [];
if (addCount > 0) {
const tip = `${addCount} ${addCount === 1 ? 'Datei' : 'Dateien'} hinzugefügt`;
parts.push(`<span class="git-badge git-add" title="${esc(tip)}">+${addCount}</span>`);
}
if (editCount > 0) {
const tip = `${editCount} ${editCount === 1 ? 'Datei' : 'Dateien'} geändert`;
parts.push(`<span class="git-badge git-edit" title="${esc(tip)}">~${editCount}</span>`);
}
if (deleteCount > 0) {
const tip = `${deleteCount} ${deleteCount === 1 ? 'Datei' : 'Dateien'} gelöscht`;
parts.push(`<span class="git-badge git-delete" title="${esc(tip)}">-${deleteCount}</span>`);
}
if (outgoing > 0) {
const tip = `${outgoing} ${outgoing === 1 ? 'Commit muss' : 'Commits müssen'} noch gepusht werden`;
parts.push(`<span class="git-badge git-outgoing" title="${esc(tip)}"><i class="fa fa-arrow-up"></i> ${outgoing}</span>`);
}
const gitEl = document.createElement('span');
gitEl.className = 'card-meta-item card-git-status';
gitEl.innerHTML = parts.join(' ');
metaEl.appendChild(gitEl);
});
}
}
@@ -154,14 +148,8 @@ registerPage('dashboard', {
const agentGrid = document.getElementById('agentCards');
agentGrid.innerHTML = '';
// Skills pro Agent parallel laden
const agentSkillsMap = {};
await Promise.all(agents.map(async a => {
agentSkillsMap[a.agentId] = await apiGetAgentSkills(a.agentId);
}));
for (const a of agents) {
const skills = agentSkillsMap[a.agentId] || [];
const skills = a.skills || [];
const card = buildAgentCard(a, skills);
// Menü zum title-row hinzufügen
@@ -213,7 +201,6 @@ registerPage('dashboard', {
document.getElementById('newAgentBtn').addEventListener('click', () => navigateTo('agent'));
// Cronjobs
const cronjobs = await apiGetCronjobs();
const cronjobGrid = document.getElementById('cronjobCards');
cronjobGrid.innerHTML = '';
@@ -303,7 +290,6 @@ registerPage('dashboard', {
document.getElementById('newCronjobBtn').addEventListener('click', () => navigateTo('cronjob'));
// Shared Links
const shares = await apiListShares();
const sharedSection = document.getElementById('sharedLinksSection');
const sharedGrid = document.getElementById('sharedLinksCards');
if (shares.length > 0) {
+3 -3
View File
@@ -89,9 +89,9 @@ registerPage('session', {
for (const a of agents) agentMap[a.agentId] = a;
const agentSkillsMap = {};
await Promise.all(agents.map(async a => {
agentSkillsMap[a.agentId] = await apiGetAgentSkills(a.agentId);
}));
for (const a of agents) {
agentSkillsMap[a.agentId] = a.skills || [];
}
function renderAgentInCard(cardEl, agent, emptyIcon, emptyText) {
cardEl.innerHTML = '';