Files
gott/wwwroot/js/pages/dashboard.js
T
Meleki 81714b1f4c Dashboard: Claude API Status-Bar
- Zeigt Anthropic API Status aus status.claude.com an
- Nur sichtbar bei aktiven Incidents (minor/major/critical)
- Farbcodierte Indikatoren (gelb=warning, rot=danger)
- Stille Fehlerbehandlung bei nicht erreichbarem Status-Endpoint
2026-03-03 05:53:39 +01:00

255 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ── Dashboard Page ──
registerPage('dashboard', {
async init() {
// Claude Status laden
loadClaudeStatus();
// Einstellungen-Link
document.getElementById('dashboardSettingsLink').addEventListener('click', (e) => {
e.preventDefault();
navigateTo('settings');
});
// Profil-Link
document.getElementById('dashboardProfileLink').addEventListener('click', (e) => {
e.preventDefault();
navigateTo('profile');
});
// Globaler Click-Handler zum Schließen aller Card-Menüs
document.addEventListener('click', this._closeCardMenus = () => closeAllCardMenus());
const agents = await apiGetAgents();
const agentMap = {};
for (const a of agents) agentMap[a.agentId] = a;
// Sessions
const sessions = await apiGetSessions();
const grid = document.getElementById('sessionCards');
grid.innerHTML = '';
for (const s of sessions) {
const agent = s.agentId ? agentMap[s.agentId] : null;
const card = document.createElement('div');
card.className = 'card';
const msgLabel = s.messageCount !== undefined
? `${s.messageCount}${s.totalMessageCount !== s.messageCount ? ` (${s.totalMessageCount})` : ''} Nachrichten`
: '';
const workspaceLabel = s.workspace ? s.workspace : '';
const sessionBadges = [];
if (agent) sessionBadges.push(`<span class="badge">${esc(agent.name)}</span>`);
if (s.context) sessionBadges.push(`<span class="badge badge-feature" title="${esc(s.context)}"><i class="fa fa-file-text-o"></i></span>`);
card.innerHTML = `
<div class="card-image">${s.image ? `<img src="${s.image}" alt="">` : ''}</div>
<div class="card-body">
<div class="card-title-row">
<span class="card-title">${esc(s.name || 'Unbenannt')}</span>
${sessionBadges.join('')}
<div class="card-menu">
<button class="card-menu-trigger icon-btn"><i class="fa fa-ellipsis-v"></i></button>
<div class="card-menu-dropdown" hidden>
<button class="card-menu-item" data-action="chat"><i class="fa fa-comment"></i> Chat öffnen</button>
<button class="card-menu-item" data-action="edit"><i class="fa fa-pencil"></i> Bearbeiten</button>
</div>
</div>
</div>
<div class="card-meta">
${msgLabel ? `<span class="card-meta-item"><i class="fa fa-comment"></i> ${esc(msgLabel)}</span>` : ''}
${workspaceLabel ? `<span class="card-meta-item card-workspace" title="${esc(workspaceLabel)}"><i class="fa fa-folder"></i> ${esc(workspaceLabel)}</span>` : ''}
</div>
</div>
`;
// Chat deaktivieren wenn kein Agent gesetzt
const chatBtn = card.querySelector('[data-action="chat"]');
if (!agent) {
chatBtn.disabled = true;
chatBtn.title = 'Kein Agent zugewiesen';
}
const menuTrigger = card.querySelector('.card-menu-trigger');
const menuDropdown = card.querySelector('.card-menu-dropdown');
menuTrigger.addEventListener('click', (e) => {
e.stopPropagation();
closeAllCardMenus();
menuDropdown.hidden = !menuDropdown.hidden;
});
chatBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (chatBtn.disabled) return;
menuDropdown.hidden = true;
navigateTo('chat', { id: s.sessionId });
});
menuDropdown.querySelector('[data-action="edit"]').addEventListener('click', (e) => {
e.stopPropagation();
menuDropdown.hidden = true;
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);
});
}
}
// Neue Session Button
document.getElementById('newSessionBtn').addEventListener('click', () => navigateTo('session'));
// Agents
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 card = document.createElement('div');
card.className = 'card';
const badges = [];
if (a.toolChoice === 'any') badges.push('<span class="badge badge-forced-feature" title="Tool-Nutzung erzwingen"><i class="fa fa-wrench"></i> Erzwungen</span>');
if (a.autoCommit === 'mention') badges.push('<span class="badge badge-feature" title="Git-Commits nur auf Anweisung"><i class="fa fa-code-fork"></i> Anmerken</span>');
if (a.autoCommit === 'enforce') badges.push('<span class="badge badge-forced-feature" title="Git-Commits nach jeder Änderung erzwungen"><i class="fa fa-code-fork"></i> Erzwungen</span>');
if (a.autoCommit === 'forbid') badges.push('<span class="badge badge-forced-feature" title="Git-Commits strikt verboten"><i class="fa fa-code-fork"></i> Verboten</span>');
if (a.useSoul) badges.push('<span class="badge badge-feature" title="Hat eine Seele (SOUL.md)"><i class="fa fa-heart"></i> Seele</span>');
if (a.useIdentity) badges.push('<span class="badge badge-feature" title="Hat eine Identität (IDENTITY.md)"><i class="fa fa-id-badge"></i> Ich</span>');
if (a.useUserInfo) badges.push('<span class="badge badge-feature" title="Kennt sein Gegenüber (Benutzer-Persona)"><i class="fa fa-user"></i> Du</span>');
if (a.useMemory) badges.push('<span class="badge badge-feature" title="Zugriff auf Erinnerungen (Memory-Tools)"><i class="fa fa-database"></i> Erinnerungen</span>');
if (a.webSearchVersion) {
const wsYear = a.webSearchVersion.includes('2025') ? 'Version 2025' : 'v2026';
badges.push(`<span class="badge badge-feature" title="Web-Suche - ${wsYear}"><i class="fa fa-search"></i> Web-Suche</span>`);
}
const skills = agentSkillsMap[a.agentId] || [];
for (const skill of skills) {
badges.push(`<span class="badge badge-skill" title="${esc(skill.description || skill.slug)}"><i class="fa fa-puzzle-piece"></i> ${esc(skill.name)}</span>`);
}
card.innerHTML = `
<div class="card-image">${a.avatar ? `<img src="${a.avatar}" alt="">` : ''}</div>
<div class="card-body">
<div class="card-title-row">
<span class="card-title">${esc(a.name)}</span>
<span class="badge">${prettyModelName(a.model)}</span>
<div class="card-menu">
<button class="card-menu-trigger icon-btn"><i class="fa fa-ellipsis-v"></i></button>
<div class="card-menu-dropdown" hidden>
<button class="card-menu-item" data-action="edit"><i class="fa fa-pencil"></i> Bearbeiten</button>
<button class="card-menu-item" data-action="files"><i class="fa fa-folder"></i> Dateien verwalten</button>
</div>
</div>
</div>
<div class="card-badges">${badges.join('')}</div>
</div>
`;
const menuTrigger = card.querySelector('.card-menu-trigger');
const menuDropdown = card.querySelector('.card-menu-dropdown');
menuTrigger.addEventListener('click', (e) => {
e.stopPropagation();
closeAllCardMenus();
menuDropdown.hidden = !menuDropdown.hidden;
});
menuDropdown.querySelector('[data-action="files"]').addEventListener('click', (e) => {
e.stopPropagation();
menuDropdown.hidden = true;
openFileExplorer({ agentId: a.agentId });
});
menuDropdown.querySelector('[data-action="edit"]').addEventListener('click', (e) => {
e.stopPropagation();
menuDropdown.hidden = true;
navigateTo('agent', { id: a.agentId });
});
agentGrid.appendChild(card);
}
// Neuer Agent Button
document.getElementById('newAgentBtn').addEventListener('click', () => navigateTo('agent'));
},
destroy() {
if (this._closeCardMenus) {
document.removeEventListener('click', this._closeCardMenus);
this._closeCardMenus = null;
}
}
});
async function loadClaudeStatus() {
const statusBar = document.getElementById('claudeStatusBar');
if (!statusBar) return;
try {
const res = await fetch('https://status.claude.com/api/v2/status.json');
if (!res.ok) return;
const data = await res.json();
const indicator = data.status?.indicator || 'none';
const description = data.status?.description || '';
// Nur anzeigen wenn es kein "none" ist (alles OK)
if (indicator === 'none') return;
statusBar.classList.add(`status-${indicator}`);
statusBar.querySelector('.status-text').textContent = description;
statusBar.hidden = false;
} catch {
// Status-Abruf fehlgeschlagen – still ignorieren
}
}
function closeAllCardMenus() {
document.querySelectorAll('.card-menu-dropdown').forEach(d => d.hidden = true);
}
function esc(s) {
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function formatDate(iso) {
if (!iso) return '';
return new Date(iso).toLocaleString('de-AT', {
day: '2-digit', month: '2-digit', year: '2-digit',
hour: '2-digit', minute: '2-digit'
});
}