// ── 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-Link (nur für Admins sichtbar) var adminLink = document.getElementById('dashboardAdminLink'); if (currentUser && currentUser.isAdmin) { adminLink.hidden = false; adminLink.addEventListener('click', (e) => { e.preventDefault(); navigateTo('admin'); }); } // 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 = document.createElement('div'); card.className = 'card'; const msgLabel1 = s.messageCount ? `${s.messageCount} Nachrichten` : ''; const msgLabel2 = s.archiveMessageCount ? `${s.archiveMessageCount} Nachrichten archiviert` : ''; const workspaceLabel = s.workspace ? s.workspace : ''; const sessionBadges = []; if (agent) sessionBadges.push(`${esc(agent.name)}`); if (s.context) sessionBadges.push(``); if (s.telegramConfigured) sessionBadges.push(``); const scaleStyle = s.avatarScale && s.avatarScale !== 1 ? ` style="scale: ${s.avatarScale}"` : ''; card.innerHTML = `
${s.image ? `` : ''}
${esc(s.name || 'Unbenannt')} ${sessionBadges.join('')}
${msgLabel1 ? ` ${esc(msgLabel1)}` : ''} ${msgLabel2 ? ` ${esc(msgLabel2)}` : ''} ${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'; } // 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 card = document.createElement('div'); card.className = 'card'; const badges = []; if (a.toolChoice === 'any') badges.push(' Erzwungen'); if (a.autoCommit === 'mention') badges.push(' Anmerken'); if (a.autoCommit === 'enforce') badges.push(' Erzwungen'); if (a.autoCommit === 'forbid') badges.push(' Verboten'); if (a.useSoul) badges.push(' Seele'); if (a.useIdentity) badges.push(' Ich'); if (a.useUserInfo) badges.push(' Du'); if (a.useMemory) badges.push(' Erinnerungen'); if (a.webSearchVersion) { const wsYear = a.webSearchVersion.includes('2025') ? 'Version 2025' : 'v2026'; badges.push(` Web-Suche`); } const skills = agentSkillsMap[a.agentId] || []; for (const skill of skills) { badges.push(` ${esc(skill.name)}`); } const agentScaleStyle = a.avatarScale && a.avatarScale !== 1 ? ` style="transform: scale(${a.avatarScale})"` : ''; card.innerHTML = `
${a.avatar ? `` : ''}
${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')); // 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 = `
${esc(cj.name)} ${statusBadge}
${esc(cronDesc)} ${cj.sessionName ? ` ${esc(cj.sessionName)}` : ''} ${lastRunIcon ? `${lastRunIcon} ${cj.lastRunAt ? formatDate(cj.lastRunAt) : ''}` : ''}
`; const menuTrigger = card.querySelector('.card-menu-trigger'); const menuDropdown = card.querySelector('.card-menu-dropdown'); menuTrigger.addEventListener('click', (e) => { e.stopPropagation(); closeAllCardMenus(); const rect = menuTrigger.getBoundingClientRect(); menuDropdown.hidden = false; const dropRect = menuDropdown.getBoundingClientRect(); menuDropdown.style.top = `${rect.bottom + 4}px`; menuDropdown.style.left = `${rect.right - dropRect.width}px`; if (rect.bottom + 4 + dropRect.height > window.innerHeight) { menuDropdown.style.top = `${rect.top - dropRect.height - 4}px`; } const finalRect = menuDropdown.getBoundingClientRect(); if (finalRect.left < 0) menuDropdown.style.left = '8px'; }); menuDropdown.querySelector('[data-action="edit"]').addEventListener('click', (e) => { e.stopPropagation(); menuDropdown.hidden = true; navigateTo('cronjob', { id: cj.cronjobId }); }); menuDropdown.querySelector('[data-action="toggle"]').addEventListener('click', async (e) => { e.stopPropagation(); menuDropdown.hidden = true; await apiToggleCronjob(cj.cronjobId); showToast(cj.isEnabled ? 'Cronjob pausiert' : 'Cronjob aktiviert'); navigateTo('dashboard', {}, { replace: true }); }); menuDropdown.querySelector('[data-action="run"]').addEventListener('click', async (e) => { e.stopPropagation(); menuDropdown.hidden = true; showToast('Cronjob wird ausgeführt...'); const result = await apiRunCronjob(cj.cronjobId); if (result.status === 'success') { showToast('Cronjob erfolgreich ausgeführt'); } else { showToast('Fehler: ' + (result.error || 'Unbekannt'), 'error'); } navigateTo('dashboard', {}, { replace: true }); }); cronjobGrid.appendChild(card); } // Neuer Cronjob Button 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) { sharedSection.hidden = false; sharedGrid.innerHTML = ''; for (const s of shares) { const card = document.createElement('div'); card.className = 'card card-compact'; const shareUrl = `${location.origin}/#shared/${s.shareToken}`; const modeLabel = s.includeContext ? 'Thread' : 'Einzeln'; const roleIcon = s.role === 'user' ? 'fa-user' : 'fa-cog'; card.innerHTML = `
${esc(s.preview || '(leer)')}
${s.sessionName ? ` ${esc(s.sessionName)}` : ''} ${modeLabel} ${formatDate(s.createdAt)}
`; const menuTrigger = card.querySelector('.card-menu-trigger'); const menuDropdown = card.querySelector('.card-menu-dropdown'); menuTrigger.addEventListener('click', (e) => { e.stopPropagation(); closeAllCardMenus(); const rect = menuTrigger.getBoundingClientRect(); menuDropdown.hidden = false; const dropRect = menuDropdown.getBoundingClientRect(); menuDropdown.style.top = `${rect.bottom + 4}px`; menuDropdown.style.left = `${rect.right - dropRect.width}px`; if (rect.bottom + 4 + dropRect.height > window.innerHeight) { menuDropdown.style.top = `${rect.top - dropRect.height - 4}px`; } const finalRect = menuDropdown.getBoundingClientRect(); if (finalRect.left < 0) menuDropdown.style.left = '8px'; }); menuDropdown.querySelector('[data-action="copy"]').addEventListener('click', async (e) => { e.stopPropagation(); menuDropdown.hidden = true; await navigator.clipboard.writeText(shareUrl); showToast('Link kopiert!'); }); menuDropdown.querySelector('[data-action="open"]').addEventListener('click', (e) => { e.stopPropagation(); menuDropdown.hidden = true; window.open(shareUrl, '_blank'); }); menuDropdown.querySelector('[data-action="delete"]').addEventListener('click', async (e) => { e.stopPropagation(); menuDropdown.hidden = true; await apiDeleteShare(s.shareToken); card.remove(); if (sharedGrid.children.length === 0) sharedSection.hidden = true; showToast('Link gelöscht'); }); sharedGrid.appendChild(card); } } }, destroy() { if (this._closeCardMenus) { document.removeEventListener('click', this._closeCardMenus); this._closeCardMenus = null; } } }); 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' }); }