// K8s Agent Dashboard - Frontend JavaScript const API_BASE = '/api'; // State let currentView = 'status'; // Initialize document.addEventListener('DOMContentLoaded', () => { setupNavigation(); loadAllData(); // Refresh data every 30 seconds setInterval(loadAllData, 30000); }); // Navigation function setupNavigation() { document.querySelectorAll('.nav-btn').forEach(btn => { btn.addEventListener('click', () => { const view = btn.dataset.view; switchView(view); }); }); } function switchView(view) { currentView = view; // Update nav buttons document.querySelectorAll('.nav-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.view === view); }); // Update views document.querySelectorAll('.view').forEach(v => { v.classList.toggle('active', v.id === `${view}-view`); }); } // Data Loading async function loadAllData() { try { await Promise.all([ loadClusterStatus(), loadPendingActions(), loadHistory(), loadWorkflows() ]); updateLastUpdate(); } catch (error) { console.error('Error loading data:', error); } } async function loadClusterStatus() { try { const response = await fetch(`${API_BASE}/status`); const data = await response.json(); renderClusterStatus(data); } catch (error) { console.error('Error loading status:', error); } } async function loadPendingActions() { try { const response = await fetch(`${API_BASE}/pending`); const data = await response.json(); renderPendingActions(data.actions || []); document.getElementById('pending-count').textContent = data.count || 0; } catch (error) { console.error('Error loading pending:', error); } } async function loadHistory() { try { const response = await fetch(`${API_BASE}/history?limit=20`); const data = await response.json(); renderHistory(data.history || []); } catch (error) { console.error('Error loading history:', error); } } async function loadWorkflows() { try { const response = await fetch(`${API_BASE}/workflows`); const data = await response.json(); renderWorkflows(data.workflows || []); } catch (error) { console.error('Error loading workflows:', error); } } // Rendering function renderClusterStatus(status) { // Update health indicator const healthEl = document.getElementById('cluster-health'); const indicator = healthEl.querySelector('.health-indicator'); const text = healthEl.querySelector('.health-text'); const health = (status.health || 'Unknown').toLowerCase(); indicator.className = `health-indicator ${health}`; text.textContent = status.health || 'Unknown'; // Render nodes const nodesBody = document.querySelector('#nodes-table tbody'); if (status.nodes && status.nodes.length > 0) { nodesBody.innerHTML = status.nodes.map(node => ` ${node.name} ${node.status}
${node.cpu_percent.toFixed(0)}%
${node.memory_percent.toFixed(0)}% ${node.conditions} `).join(''); } else { nodesBody.innerHTML = 'No nodes data available'; } // Render alerts const alertsList = document.getElementById('alerts-list'); if (status.alerts && status.alerts.length > 0) { alertsList.innerHTML = status.alerts.map(alert => `
[${alert.severity.toUpperCase()}] ${alert.name} ${alert.description}
`).join(''); } else { alertsList.innerHTML = '

No active alerts

'; } // Render apps const appsBody = document.querySelector('#apps-table tbody'); if (status.apps && status.apps.length > 0) { appsBody.innerHTML = status.apps.map(app => ` ${app.name} ${app.sync_status} ${app.health} ${app.revision.substring(0, 7)} `).join(''); } else { appsBody.innerHTML = 'No ArgoCD apps data available'; } } function renderPendingActions(actions) { const list = document.getElementById('pending-list'); if (actions.length === 0) { list.innerHTML = '

No pending actions

'; return; } list.innerHTML = actions.map(action => `
${action.agent}
${action.action}
${action.risk} risk
${action.description}
`).join(''); } function renderHistory(history) { const tbody = document.querySelector('#history-table tbody'); if (history.length === 0) { tbody.innerHTML = 'No history available'; return; } tbody.innerHTML = history.map(entry => ` ${formatTime(entry.timestamp)} ${entry.agent} ${entry.action} ${entry.result} `).join(''); } function renderWorkflows(workflows) { const list = document.getElementById('workflows-list'); if (workflows.length === 0) { list.innerHTML = '

No workflows defined

'; return; } list.innerHTML = workflows.map(wf => `

${wf.name}

${wf.description}

${wf.triggers.map(t => `${t}`).join('')}
`).join(''); } // Actions async function approveAction(id) { try { const response = await fetch(`${API_BASE}/pending/${id}/approve`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); if (response.ok) { loadPendingActions(); loadHistory(); } else { alert('Failed to approve action'); } } catch (error) { console.error('Error approving action:', error); alert('Error approving action'); } } async function rejectAction(id) { const reason = prompt('Reason for rejection (optional):'); try { const response = await fetch(`${API_BASE}/pending/${id}/reject`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reason: reason || 'Rejected by user' }) }); if (response.ok) { loadPendingActions(); loadHistory(); } else { alert('Failed to reject action'); } } catch (error) { console.error('Error rejecting action:', error); alert('Error rejecting action'); } } async function runWorkflow(name) { try { const response = await fetch(`${API_BASE}/workflows/${name}/run`, { method: 'POST' }); const data = await response.json(); alert(data.message); } catch (error) { console.error('Error running workflow:', error); alert('Error running workflow'); } } // Helpers function getProgressClass(percent) { if (percent >= 80) return 'danger'; if (percent >= 60) return 'warning'; return ''; } function formatTime(timestamp) { const date = new Date(timestamp); return date.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } function updateLastUpdate() { const now = new Date(); document.getElementById('last-update').textContent = now.toLocaleTimeString(); }