// ── Dashboard Page ── registerPage('dashboard', { async init() { // Einstellungen-Link document.getElementById('dashboardSettingsLink').addEventListener('click', (e) => { e.preventDefault(); navigateTo('settings'); }); // Profil-Link document.getElementById('dashboardProfileLink').addEventListener('click', (e) => { e.preventDefault(); navigateTo('profile'); }); // Admin-Links (nur für Admins sichtbar) var adminLink = document.getElementById('dashboardAdminLink'); var serverSettingsLink = document.getElementById('dashboardServerSettingsLink'); if (currentUser && currentUser.isAdmin) { adminLink.hidden = false; adminLink.addEventListener('click', (e) => { e.preventDefault(); navigateTo('admin'); }); serverSettingsLink.hidden = false; serverSettingsLink.addEventListener('click', (e) => { e.preventDefault(); navigateTo('server-settings'); }); } // Globaler Click-Handler zum Schließen aller Card-Menüs document.addEventListener('click', this._closeCardMenus = () => closeAllCardMenus()); // Telegram-Config parallel pro Session laden (sessions werden gleich geladen) 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 = buildSessionCard(s, agent); // Menü zum title-row hinzufügen const titleRow = card.querySelector('.card-title-row'); titleRow.insertAdjacentHTML('beforeend', `
`); // Chat deaktivieren wenn kein Agent gesetzt const chatBtn = card.querySelector('[data-action="chat"]'); if (!agent) { chatBtn.disabled = true; chatBtn.title = 'Kein Agent zugewiesen'; } // Card selbst klickbar wenn Agent zugewiesen if (agent) { card.classList.add('clickable'); card.addEventListener('click', () => navigateTo('chat', { id: s.sessionId })); } 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 }); }); 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(`+${addCount}`); } if (editCount > 0) { const tip = `${editCount} ${editCount === 1 ? 'Datei' : 'Dateien'} geändert`; parts.push(`~${editCount}`); } if (deleteCount > 0) { const tip = `${deleteCount} ${deleteCount === 1 ? 'Datei' : 'Dateien'} gelöscht`; parts.push(`-${deleteCount}`); } if (outgoing > 0) { const tip = `${outgoing} ${outgoing === 1 ? 'Commit muss' : 'Commits müssen'} noch gepusht werden`; parts.push(` ${outgoing}`); } 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 skills = agentSkillsMap[a.agentId] || []; const card = buildAgentCard(a, skills); // Menü zum title-row hinzufügen const titleRow = card.querySelector('.card-title-row'); const menuHtml = ``; titleRow.insertAdjacentHTML('beforeend', menuHtml); 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')); // Cronjobs const cronjobs = await apiGetCronjobs(); const cronjobGrid = document.getElementById('cronjobCards'); cronjobGrid.innerHTML = ''; for (const cj of cronjobs) { const card = document.createElement('div'); card.className = 'card card-compact'; const statusBadge = cj.isEnabled ? ' Aktiv' : ' Pausiert'; const lastRunIcon = cj.lastRunStatus === 'success' ? '✅' : cj.lastRunStatus === 'error' ? '❌' : ''; const cronDesc = describeCron(cj.cronExpression) || cj.cronExpression; card.innerHTML = `