// API endpoints const API_BASE_URL = '/api/claude'; const ENDPOINTS = { listJobs: `${API_BASE_URL}/list-jobs`, manageJob: `${API_BASE_URL}/jobs`, jobLogs: `${API_BASE_URL}/job-logs` }; // DOM elements const elements = { namespaceSelector: document.getElementById('namespace-selector'), refreshBtn: document.getElementById('refresh-btn'), jobList: document.getElementById('job-list'), jobTable: document.getElementById('job-table'), jobDetails: document.getElementById('job-details'), logContent: document.getElementById('log-content'), logTabs: document.querySelectorAll('.log-tab'), loading: document.getElementById('loading'), errorMessage: document.getElementById('error-message') }; // State let state = { jobs: [], selectedJob: null, selectedNamespace: 'development', logs: { stdout: '', stderr: '', currentTab: 'stdout' } }; // Initialize the app function init() { // Set up event listeners elements.namespaceSelector.addEventListener('change', handleNamespaceChange); elements.refreshBtn.addEventListener('click', loadJobs); elements.logTabs.forEach(tab => { tab.addEventListener('click', () => { const logType = tab.getAttribute('data-log-type'); switchLogTab(logType); }); }); // Load initial jobs loadJobs(); } // Load jobs from the API async function loadJobs() { showLoading(true); hideError(); try { const namespace = elements.namespaceSelector.value; const response = await fetch(`${ENDPOINTS.listJobs}?namespace=${namespace}`); if (!response.ok) { throw new Error(`Failed to load jobs: ${response.statusText}`); } const jobs = await response.json(); state.jobs = jobs; state.selectedNamespace = namespace; renderJobList(); showLoading(false); } catch (error) { console.error('Error loading jobs:', error); showError(`Failed to load jobs: ${error.message}`); showLoading(false); } } // Render the job list function renderJobList() { elements.jobList.innerHTML = ''; if (state.jobs.length === 0) { const row = document.createElement('tr'); row.innerHTML = `No jobs found in the ${state.selectedNamespace} namespace`; elements.jobList.appendChild(row); return; } state.jobs.forEach(job => { const row = document.createElement('tr'); row.setAttribute('data-job-id', job.id); row.innerHTML = ` ${job.id} ${job.type} ${job.status} `; elements.jobList.appendChild(row); }); // Add event listeners to buttons document.querySelectorAll('.btn-view').forEach(btn => { btn.addEventListener('click', () => viewJob(btn.getAttribute('data-job-id'))); }); document.querySelectorAll('.btn-restart').forEach(btn => { btn.addEventListener('click', () => restartJob(btn.getAttribute('data-job-id'))); }); document.querySelectorAll('.btn-stop').forEach(btn => { btn.addEventListener('click', () => stopJob(btn.getAttribute('data-job-id'))); }); } // View job details async function viewJob(jobId) { showLoading(true); try { // Get job status const statusResponse = await fetch(ENDPOINTS.manageJob, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ job_id: jobId, action: 'status', namespace: state.selectedNamespace }) }); if (!statusResponse.ok) { throw new Error(`Failed to get job status: ${statusResponse.statusText}`); } const jobStatus = await statusResponse.json(); state.selectedJob = jobStatus; // Get job logs const logsResponse = await fetch(`${ENDPOINTS.jobLogs}/${jobId}?namespace=${state.selectedNamespace}`); if (logsResponse.ok) { const logsData = await logsResponse.json(); if (logsData.success) { state.logs.stdout = logsData.logs.stdout || 'No stdout logs available'; state.logs.stderr = logsData.logs.stderr || 'No stderr logs available'; } else { state.logs.stdout = 'Logs not available'; state.logs.stderr = 'Logs not available'; } } else { state.logs.stdout = 'Failed to load logs'; state.logs.stderr = 'Failed to load logs'; } renderJobDetails(); renderLogs(); showLoading(false); // Highlight the selected job in the table document.querySelectorAll('#job-list tr').forEach(row => { row.classList.remove('selected'); }); const selectedRow = document.querySelector(`#job-list tr[data-job-id="${jobId}"]`); if (selectedRow) { selectedRow.classList.add('selected'); } } catch (error) { console.error('Error viewing job:', error); showError(`Failed to view job: ${error.message}`); showLoading(false); } } // Restart a job async function restartJob(jobId) { if (!confirm(`Are you sure you want to restart job "${jobId}"?`)) { return; } showLoading(true); try { const response = await fetch(ENDPOINTS.manageJob, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ job_id: jobId, action: 'restart', namespace: state.selectedNamespace }) }); if (!response.ok) { throw new Error(`Failed to restart job: ${response.statusText}`); } const result = await response.json(); if (result.success) { alert(`Job "${jobId}" has been restarted successfully.`); loadJobs(); } else { throw new Error(result.message); } showLoading(false); } catch (error) { console.error('Error restarting job:', error); showError(`Failed to restart job: ${error.message}`); showLoading(false); } } // Stop a job async function stopJob(jobId) { const purge = confirm(`Do you want to purge job "${jobId}" after stopping?`); if (!confirm(`Are you sure you want to stop job "${jobId}"?`)) { return; } showLoading(true); try { const response = await fetch(ENDPOINTS.manageJob, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ job_id: jobId, action: 'stop', namespace: state.selectedNamespace, purge: purge }) }); if (!response.ok) { throw new Error(`Failed to stop job: ${response.statusText}`); } const result = await response.json(); if (result.success) { alert(`Job "${jobId}" has been stopped${purge ? ' and purged' : ''} successfully.`); loadJobs(); } else { throw new Error(result.message); } showLoading(false); } catch (error) { console.error('Error stopping job:', error); showError(`Failed to stop job: ${error.message}`); showLoading(false); } } // Render job details function renderJobDetails() { if (!state.selectedJob) { elements.jobDetails.innerHTML = '

Select a job to view details

'; return; } const job = state.selectedJob; const details = job.details?.job || {}; const allocation = job.details?.latest_allocation || {}; let detailsHtml = `

${job.job_id}

Status: ${job.status}

`; if (details.Type) { detailsHtml += `

Type: ${details.Type}

`; } if (details.Namespace) { detailsHtml += `

Namespace: ${details.Namespace}

`; } if (details.Datacenters) { detailsHtml += `

Datacenters: ${details.Datacenters.join(', ')}

`; } if (allocation.ID) { detailsHtml += `

Latest Allocation

ID: ${allocation.ID}

Status: ${allocation.ClientStatus || 'Unknown'}

`; if (allocation.ClientDescription) { detailsHtml += `

Description: ${allocation.ClientDescription}

`; } } elements.jobDetails.innerHTML = detailsHtml; } // Render logs function renderLogs() { elements.logContent.textContent = state.logs[state.logs.currentTab]; } // Switch log tab function switchLogTab(logType) { state.logs.currentTab = logType; // Update active tab elements.logTabs.forEach(tab => { if (tab.getAttribute('data-log-type') === logType) { tab.classList.add('active'); } else { tab.classList.remove('active'); } }); renderLogs(); } // Handle namespace change function handleNamespaceChange() { loadJobs(); } // Show/hide loading indicator function showLoading(show) { elements.loading.style.display = show ? 'block' : 'none'; elements.jobTable.style.display = show ? 'none' : 'table'; } // Show error message function showError(message) { elements.errorMessage.textContent = message; elements.errorMessage.style.display = 'block'; } // Hide error message function hideError() { elements.errorMessage.style.display = 'none'; } // Initialize the app when the DOM is loaded document.addEventListener('DOMContentLoaded', init);