// ── 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());
// Telegram-Status parallel laden
const telegramStatusPromise = apiTelegramStatus().catch(() => ({ configured: false }));
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(`${esc(agent.name)}`);
if (s.context) sessionBadges.push(``);
card.innerHTML = `
${esc(s.name || 'Unbenannt')}
${sessionBadges.join('')}
${msgLabel ? ` ${esc(msgLabel)}` : ''}
${workspaceLabel ? ` ${esc(workspaceLabel)}` : ''}
`;
// 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();
// Position fixed dropdown below trigger, aligned to the left
const rect = menuTrigger.getBoundingClientRect();
menuDropdown.hidden = false;
const dropRect = menuDropdown.getBoundingClientRect();
// Position: below trigger, right edge aligned with trigger's right edge
menuDropdown.style.top = `${rect.bottom + 4}px`;
menuDropdown.style.left = `${rect.right - dropRect.width}px`;
// Adjust if off-screen bottom
if (rect.bottom + 4 + dropRect.height > window.innerHeight) {
menuDropdown.style.top = `${rect.top - dropRect.height - 4}px`;
}
// Safety: if off-screen left
const finalRect = menuDropdown.getBoundingClientRect();
if (finalRect.left < 0) {
menuDropdown.style.left = '8px';
}
});
chatBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (chatBtn.disabled) return;
menuDropdown.hidden = true;
navigateTo('chat', { id: s.sessionId });
});
const telegramBtn = menuDropdown.querySelector('[data-action="telegram"]');
telegramBtn.addEventListener('click', async (e) => {
e.stopPropagation();
menuDropdown.hidden = true;
try {
const result = await apiTelegramLink(s.sessionId);
showTelegramLinkCode(result.code, s.name || 'Unbenannt');
} catch (err) {
showToast(err.message, 'error');
}
});
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(`
${esc(a.name)}
${prettyModelName(a.model)}
${badges.join('')}
`;
const menuTrigger = card.querySelector('.card-menu-trigger');
const menuDropdown = card.querySelector('.card-menu-dropdown');
menuTrigger.addEventListener('click', (e) => {
e.stopPropagation();
closeAllCardMenus();
// Position fixed dropdown below trigger, aligned to the left
const rect = menuTrigger.getBoundingClientRect();
menuDropdown.hidden = false;
const dropRect = menuDropdown.getBoundingClientRect();
// Position: below trigger, right edge aligned with trigger's right edge
menuDropdown.style.top = `${rect.bottom + 4}px`;
menuDropdown.style.left = `${rect.right - dropRect.width}px`;
// Adjust if off-screen bottom
if (rect.bottom + 4 + dropRect.height > window.innerHeight) {
menuDropdown.style.top = `${rect.top - dropRect.height - 4}px`;
}
// Safety: if off-screen left
const finalRect = menuDropdown.getBoundingClientRect();
if (finalRect.left < 0) {
menuDropdown.style.left = '8px';
}
});
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 showTelegramLinkCode(code, sessionName) {
// Bestehendes Modal entfernen
const existing = document.getElementById('telegramLinkModal');
if (existing) existing.remove();
const modal = document.createElement('div');
modal.id = 'telegramLinkModal';
modal.className = 'modal-overlay';
modal.innerHTML = `