const API_BASE = '/api'; let currentUser = null; // Check authentication on load async function init() { await checkAuth(); setupEventListeners(); if (currentUser) { showMainPage(); loadJobs(); loadRunners(); } else { showLoginPage(); } } async function checkAuth() { try { const response = await fetch(`${API_BASE}/auth/me`); if (response.ok) { currentUser = await response.json(); return true; } } catch (error) { console.error('Auth check failed:', error); } return false; } function showLoginPage() { document.getElementById('login-page').classList.remove('hidden'); document.getElementById('main-page').classList.add('hidden'); } function showMainPage() { document.getElementById('login-page').classList.add('hidden'); document.getElementById('main-page').classList.remove('hidden'); if (currentUser) { document.getElementById('user-name').textContent = currentUser.name || currentUser.email; } } function setupEventListeners() { // Navigation document.querySelectorAll('.nav-btn').forEach(btn => { btn.addEventListener('click', (e) => { const page = e.target.dataset.page; switchPage(page); }); }); // Logout document.getElementById('logout-btn').addEventListener('click', async () => { await fetch(`${API_BASE}/auth/logout`, { method: 'POST' }); currentUser = null; showLoginPage(); }); // Job form document.getElementById('job-form').addEventListener('submit', async (e) => { e.preventDefault(); await submitJob(); }); } function switchPage(page) { document.querySelectorAll('.content-page').forEach(p => p.classList.add('hidden')); document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active')); document.getElementById(`${page}-page`).classList.remove('hidden'); document.querySelector(`[data-page="${page}"]`).classList.add('active'); if (page === 'jobs') { loadJobs(); } else if (page === 'runners') { loadRunners(); } } async function submitJob() { const form = document.getElementById('job-form'); const formData = new FormData(form); const jobData = { name: document.getElementById('job-name').value, frame_start: parseInt(document.getElementById('frame-start').value), frame_end: parseInt(document.getElementById('frame-end').value), output_format: document.getElementById('output-format').value, }; try { // Create job const jobResponse = await fetch(`${API_BASE}/jobs`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(jobData), }); if (!jobResponse.ok) { throw new Error('Failed to create job'); } const job = await jobResponse.json(); // Upload file const fileInput = document.getElementById('blend-file'); if (fileInput.files.length > 0) { const fileFormData = new FormData(); fileFormData.append('file', fileInput.files[0]); const fileResponse = await fetch(`${API_BASE}/jobs/${job.id}/upload`, { method: 'POST', body: fileFormData, }); if (!fileResponse.ok) { throw new Error('Failed to upload file'); } } alert('Job submitted successfully!'); form.reset(); switchPage('jobs'); loadJobs(); } catch (error) { alert('Failed to submit job: ' + error.message); } } async function loadJobs() { try { const response = await fetch(`${API_BASE}/jobs`); if (!response.ok) throw new Error('Failed to load jobs'); const jobs = await response.json(); displayJobs(jobs); } catch (error) { console.error('Failed to load jobs:', error); } } function displayJobs(jobs) { const container = document.getElementById('jobs-list'); if (jobs.length === 0) { container.innerHTML = '

No jobs yet. Submit a job to get started!

'; return; } container.innerHTML = jobs.map(job => `

${escapeHtml(job.name)}

Frames: ${job.frame_start}-${job.frame_end} Format: ${job.output_format} Created: ${new Date(job.created_at).toLocaleString()}
${job.status}
${job.status === 'pending' || job.status === 'running' ? `` : ''}
`).join(''); } async function viewJob(jobId) { try { const response = await fetch(`${API_BASE}/jobs/${jobId}`); if (!response.ok) throw new Error('Failed to load job'); const job = await response.json(); // Load files const filesResponse = await fetch(`${API_BASE}/jobs/${jobId}/files`); const files = filesResponse.ok ? await filesResponse.json() : []; const outputFiles = files.filter(f => f.file_type === 'output'); if (outputFiles.length > 0) { let message = 'Output files:\n'; outputFiles.forEach(file => { message += `- ${file.file_name}\n`; }); message += '\nWould you like to download them?'; if (confirm(message)) { outputFiles.forEach(file => { window.open(`${API_BASE}/jobs/${jobId}/files/${file.id}/download`, '_blank'); }); } } else { alert(`Job: ${job.name}\nStatus: ${job.status}\nProgress: ${job.progress.toFixed(1)}%`); } } catch (error) { alert('Failed to load job details: ' + error.message); } } async function cancelJob(jobId) { if (!confirm('Are you sure you want to cancel this job?')) return; try { const response = await fetch(`${API_BASE}/jobs/${jobId}`, { method: 'DELETE', }); if (!response.ok) throw new Error('Failed to cancel job'); loadJobs(); } catch (error) { alert('Failed to cancel job: ' + error.message); } } async function loadRunners() { try { const response = await fetch(`${API_BASE}/runners`); if (!response.ok) throw new Error('Failed to load runners'); const runners = await response.json(); displayRunners(runners); } catch (error) { console.error('Failed to load runners:', error); } } function displayRunners(runners) { const container = document.getElementById('runners-list'); if (runners.length === 0) { container.innerHTML = '

No runners connected.

'; return; } container.innerHTML = runners.map(runner => { const lastHeartbeat = new Date(runner.last_heartbeat); const isOnline = (Date.now() - lastHeartbeat.getTime()) < 60000; // 1 minute return `

${escapeHtml(runner.name)}

Hostname: ${escapeHtml(runner.hostname)} Last heartbeat: ${lastHeartbeat.toLocaleString()}
${isOnline ? 'Online' : 'Offline'}
`; }).join(''); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Auto-refresh jobs every 5 seconds setInterval(() => { if (currentUser && document.getElementById('jobs-page').classList.contains('hidden') === false) { loadJobs(); } }, 5000); // Initialize on load init();