// 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 = `
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 = `
`;
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 = `
`;
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 = `
`;
return;
}
container.innerHTML = agents.map(agent => `
${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') + '' +
'
' +
'
' +
'
' +
'
' +
'
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 + '
' +
'
' +
'';
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');
});
}
}