// WHOOSH Dashboard JavaScript // Global state let pulseChart = null; // Initialize on DOM load document.addEventListener('DOMContentLoaded', function() { initializeTabs(); loadDashboard(); initializePulseVisualization(); // Setup form submission handler const repositoryForm = document.getElementById('repository-form'); if (repositoryForm) { repositoryForm.addEventListener('submit', handleRepositorySubmit); } }); // Tab management function initializeTabs() { const tabs = document.querySelectorAll('.nav-tab'); tabs.forEach(tab => { tab.addEventListener('click', () => showTab(tab.dataset.tab)); }); } function showTab(tabId) { // Hide all tab contents const contents = document.querySelectorAll('.tab-content'); contents.forEach(content => { content.classList.remove('active'); }); // Remove active class from all tabs const tabs = document.querySelectorAll('.nav-tab'); tabs.forEach(tab => { tab.classList.remove('active'); }); // Show selected tab content const selectedContent = document.getElementById(tabId); if (selectedContent) { selectedContent.classList.add('active'); } // Activate selected tab const selectedTab = document.querySelector(`[data-tab="${tabId}"]`); if (selectedTab) { selectedTab.classList.add('active'); } // Load content for specific tabs switch(tabId) { case 'tasks': loadTasks(); break; case 'teams': loadTeams(); break; case 'agents': loadAgents(); break; case 'repositories': loadRepositories(); break; } } // Dashboard data loading function loadDashboard() { loadSystemMetrics(); loadRecentActivity(); loadSystemStatus(); } function loadSystemMetrics() { fetch('/api/v1/metrics') .then(response => response.json()) .then(data => { updateMetric('active-councils', data.active_councils || 0); updateMetric('deployed-agents', data.deployed_agents || 0); updateMetric('completed-tasks', data.completed_tasks || 0); }) .catch(error => { console.error('Error loading metrics:', error); }); } function loadRecentActivity() { fetch('/api/v1/activity/recent') .then(response => response.json()) .then(data => { const container = document.getElementById('recent-activity'); if (data && data.length > 0) { container.innerHTML = data.map(activity => `
${activity.title}
${activity.timestamp}
` ).join(''); } else { container.innerHTML = `
Empty

No recent activity

`; } }) .catch(error => { console.error('Error loading recent activity:', error); }); } function loadSystemStatus() { fetch('/api/v1/status') .then(response => response.json()) .then(data => { updateStatus('database', data.database || 'healthy'); updateStatus('gitea-integration', data.gitea || 'connected'); updateStatus('backbeat', data.backbeat || 'active'); }) .catch(error => { console.error('Error loading system status:', error); }); } function updateMetric(id, value) { const element = document.querySelector(`[data-metric="${id}"], .metric-value`); if (element) { element.textContent = value; } } function updateStatus(component, status) { // Status indicators are currently hardcoded in HTML console.log(`Status update: ${component} = ${status}`); } // BACKBEAT pulse visualization function initializePulseVisualization() { const canvas = document.getElementById('pulse-trace'); if (!canvas) return; const ctx = canvas.getContext('2d'); canvas.width = canvas.offsetWidth; canvas.height = 60; // Initialize pulse visualization updatePulseVisualization(); // Update every second setInterval(updatePulseVisualization, 1000); } function updatePulseVisualization() { fetch('/api/v1/backbeat/status') .then(response => response.json()) .then(data => { updateBeatMetrics(data); drawPulseTrace(data); }) .catch(error => { // Use mock data for demonstration const mockData = { tempo: Math.floor(Math.random() * 40) + 60, volume: Math.floor(Math.random() * 30) + 70, phase: ['rising', 'peak', 'falling', 'valley'][Math.floor(Math.random() * 4)], trace: Array.from({length: 50}, () => Math.random() * 100) }; updateBeatMetrics(mockData); drawPulseTrace(mockData); }); } function updateBeatMetrics(data) { const tempoEl = document.getElementById('beat-tempo'); const volumeEl = document.getElementById('beat-volume'); const phaseEl = document.getElementById('beat-phase'); if (tempoEl) tempoEl.textContent = data.tempo + ' BPM'; if (volumeEl) volumeEl.textContent = data.volume + '%'; if (phaseEl) phaseEl.textContent = data.phase; } function drawPulseTrace(data) { const canvas = document.getElementById('pulse-trace'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width; const height = canvas.height; // Clear canvas ctx.fillStyle = 'var(--carbon-800)'; ctx.fillRect(0, 0, width, height); if (!data.trace || data.trace.length === 0) return; // Draw pulse trace ctx.strokeStyle = 'var(--ocean-400)'; ctx.lineWidth = 2; ctx.beginPath(); const stepX = width / (data.trace.length - 1); data.trace.forEach((point, index) => { const x = index * stepX; const y = height - (point / 100 * height); if (index === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); } // Task management function refreshTasks() { loadTasks(); } function loadTasks() { Promise.all([ fetch('/api/v1/tasks/active').then(r => r.json()).catch(() => []), fetch('/api/v1/tasks/scheduled').then(r => r.json()).catch(() => []) ]).then(([activeTasks, scheduledTasks]) => { renderTasks('active-tasks', activeTasks); renderTasks('scheduled-tasks', scheduledTasks); }); } function renderTasks(containerId, tasks) { const container = document.getElementById(containerId); if (!container) return; if (!tasks || tasks.length === 0) { const isActive = containerId === 'active-tasks'; const icon = isActive ? 'List_Check.png' : 'Calendar.png'; const message = isActive ? 'No active tasks found' : 'No scheduled tasks found'; container.innerHTML = `
No tasks

${message}

`; return; } container.innerHTML = tasks.map(task => { const priorityClass = task.priority ? `priority-${task.priority.toLowerCase()}` : ''; return `
${task.title || 'Untitled Task'}
Priority: ${task.priority || 'Normal'} ${task.created_at || ''}
`; }).join(''); } // Team management function loadTeams() { fetch('/api/v1/teams') .then(response => response.json()) .then(teams => { renderTeams(teams); }) .catch(error => { console.error('Error loading teams:', error); renderTeams([]); }); } function renderTeams(teams) { const container = document.getElementById('teams-list'); if (!container) return; if (!teams || teams.length === 0) { container.innerHTML = `
No teams

No teams configured yet

`; return; } container.innerHTML = teams.map(team => `
${team.name}
${team.description || ''}
`).join(''); } // Agent management function loadAgents() { fetch('/api/v1/agents') .then(response => response.json()) .then(agents => { renderAgents(agents); }) .catch(error => { console.error('Error loading agents:', error); renderAgents([]); }); } function renderAgents(agents) { const container = document.getElementById('agents-list'); if (!container) return; if (!agents || agents.length === 0) { container.innerHTML = `
No agents

No agents registered yet

`; return; } container.innerHTML = agents.map(agent => `
${agent.name}
${agent.description || 'No description available'}
`).join(''); } // Repository management function showAddRepositoryForm() { document.getElementById('add-repository-form').style.display = 'block'; } function hideAddRepositoryForm() { document.getElementById('add-repository-form').style.display = 'none'; document.getElementById('repository-form').reset(); } function handleRepositorySubmit(e) { e.preventDefault(); const formData = { name: document.getElementById('repo-name').value.trim(), owner: document.getElementById('repo-owner').value.trim(), url: document.getElementById('repo-url').value.trim(), source_type: document.getElementById('repo-source-type').value, default_branch: document.getElementById('repo-branch').value.trim() || 'main', description: document.getElementById('repo-description').value.trim(), monitor_issues: document.getElementById('repo-monitor-issues').checked, enable_chorus_integration: document.getElementById('repo-enable-chorus').checked }; if (!formData.name || !formData.owner || !formData.url) { alert('Please fill in all required fields'); return; } fetch('/api/v1/repositories', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }) .then(response => response.json()) .then(data => { if (data.error) { alert('Error adding repository: ' + data.error); } else { alert('Repository added successfully!'); hideAddRepositoryForm(); loadRepositories(); updateRepositoryStats(); } }) .catch(error => { console.error('Error adding repository:', error); alert('Error adding repository'); }); } function loadRepositories() { fetch('/api/v1/repositories') .then(response => response.json()) .then(repositories => { renderRepositories(repositories); updateRepositoryStats(); }) .catch(error => { console.error('Error loading repositories:', error); renderRepositories([]); }); } function updateRepositoryStats() { fetch('/api/v1/repositories/stats') .then(response => response.json()) .then(stats => { const totalEl = document.getElementById('total-repos'); const activeEl = document.getElementById('active-repos'); const lastSyncEl = document.getElementById('last-sync'); if (totalEl) totalEl.textContent = stats.total || 0; if (activeEl) activeEl.textContent = stats.active || 0; if (lastSyncEl) lastSyncEl.textContent = stats.last_sync || 'Never'; }) .catch(error => { console.error('Error loading repository stats:', error); }); } function renderRepositories(repositories) { const container = document.getElementById('repositories-list'); if (!container) return; if (!repositories || repositories.length === 0) { container.innerHTML = '

No repositories found

'; return; } const html = repositories.map(repo => '
' + '
' + '

' + repo.full_name + '

' + '
' + '
' + '' + (repo.status || 'unknown') + '' + '
' + '
' + '
' + '
Language: ' + (repo.language || 'Not detected') + '
' + '
Default Branch: ' + (repo.default_branch || 'main') + '
' + '
Source: ' + (repo.source_type || 'git') + '
' + (repo.description ? '
Description: ' + repo.description + '
' : '') + '
' + '
' + '
Issues: ' + (repo.monitor_issues ? '✅ Monitored' : '❌ Not monitored') + '
' + '
Pull Requests: ' + (repo.monitor_pull_requests ? '✅ Monitored' : '❌ Not monitored') + '
' + '
Releases: ' + (repo.monitor_releases ? '✅ Monitored' : '❌ Not monitored') + '
' + '
CHORUS: ' + (repo.enable_chorus_integration ? '✅ Enabled' : '❌ Disabled') + '
' + '
' + '
' + '' + '' + '' + '' + '
' + '
' ).join(''); container.innerHTML = html; } function getStatusColor(status) { switch(status) { case 'active': return 'var(--eucalyptus-500)'; case 'pending': return 'var(--coral-700)'; case 'error': return 'var(--coral-500)'; case 'disabled': return 'var(--carbon-400)'; default: return 'var(--carbon-500)'; } } function syncRepository(repoId) { fetch('/api/v1/repositories/' + repoId + '/sync', { method: 'POST' }) .then(response => response.json()) .then(data => { alert('Repository sync triggered: ' + data.message); loadRepositories(); // Reload to show updated status }) .catch(error => { console.error('Error syncing repository:', error); alert('Error syncing repository'); }); } function ensureLabels(repoId) { fetch('/api/v1/repositories/' + repoId + '/ensure-labels', { method: 'POST' }) .then(response => response.json()) .then(data => { if (data.error) { alert('Error ensuring labels: ' + data.error); } else { alert('Labels ensured successfully for ' + data.owner + '/' + data.name + '\n\nRequired labels created:\n• bzzz-task\n• whoosh-monitored\n• priority-high\n• priority-medium\n• priority-low'); } }) .catch(error => { console.error('Error ensuring labels:', error); alert('Error ensuring labels'); }); } function editRepository(repoId) { // Fetch repository details first fetch('/api/v1/repositories/' + repoId) .then(response => response.json()) .then(repo => { showEditModal(repo); }) .catch(error => { console.error('Error fetching repository:', error); alert('Error fetching repository details'); }); } function showEditModal(repo) { // Create modal overlay const overlay = document.createElement('div'); overlay.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; ' + 'background: rgba(0,0,0,0.5); display: flex; align-items: center; ' + 'justify-content: center; z-index: 1000;'; // Create modal content const modal = document.createElement('div'); modal.style.cssText = 'background: white; border-radius: 8px; padding: 24px; ' + 'max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto;'; modal.innerHTML = '

Edit Repository

' + '
' + '' + repo.full_name + '' + '
ID: ' + repo.id + '
' + '
' + '
' + '
' + '' + '' + '
' + '
' + '' + '' + '
' + '
' + '' + '' + '
' + '
' + '

Monitoring Options:

' + '
' + '' + '
' + '
' + '' + '
' + '
' + '' + '
' + '
' + '
' + '

CHORUS Integration:

' + '
' + '' + '
' + '
' + '' + '
' + '
' + '
' + '' + '' + '
' + '
'; overlay.appendChild(modal); document.body.appendChild(overlay); // Store modal reference globally so we can close it window.currentEditModal = overlay; window.currentRepoId = repo.id; // Handle form submission document.getElementById('editRepoForm').addEventListener('submit', function(e) { e.preventDefault(); saveRepositoryChanges(); }); // Close modal on overlay click overlay.addEventListener('click', function(e) { if (e.target === overlay) { closeEditModal(); } }); } function closeEditModal() { if (window.currentEditModal) { document.body.removeChild(window.currentEditModal); window.currentEditModal = null; window.currentRepoId = null; } } function saveRepositoryChanges() { const formData = { description: document.getElementById('description').value.trim() || null, default_branch: document.getElementById('defaultBranch').value.trim() || null, language: document.getElementById('language').value.trim() || null, monitor_issues: document.getElementById('monitorIssues').checked, monitor_pull_requests: document.getElementById('monitorPRs').checked, monitor_releases: document.getElementById('monitorReleases').checked, enable_chorus_integration: document.getElementById('enableChorus').checked, auto_assign_teams: document.getElementById('autoAssignTeams').checked }; fetch('/api/v1/repositories/' + window.currentRepoId, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }) .then(response => response.json()) .then(data => { alert('Repository updated successfully!'); closeEditModal(); loadRepositories(); // Reload the list to show changes }) .catch(error => { console.error('Error updating repository:', error); alert('Error updating repository'); }); } function deleteRepository(repoId, fullName) { if (confirm('Are you sure you want to delete repository "' + fullName + '"? This will stop monitoring and cannot be undone.')) { fetch('/api/v1/repositories/' + repoId, { method: 'DELETE' }) .then(response => response.json()) .then(data => { alert('Repository deleted: ' + data.message); loadRepositories(); // Reload the list }) .catch(error => { console.error('Error deleting repository:', error); alert('Error deleting repository'); }); } }