// screens-jobs.jsx function Jobs({ navigate }) { const [tab, setTab] = React.useState('all'); const [jobs, setJobs] = React.useState(window.ZAMPP_DATA.JOBS); const [lastFetch, setLastFetch] = React.useState(Date.now()); const normalizeJob = (j) => { const statusMap = { waiting: 'queued', active: 'running', completed: 'done', failed: 'failed' }; const kindMap = { proxy: 'Proxy', thumbnail: 'Thumbnail', conform: 'Conform', transcode: 'Transcode' }; const meta = j.metadata || {}; return { ...j, status: statusMap[j.status] || j.status, kind: kindMap[j.type] || j.type || 'Job', asset: j.asset_name || meta.filename || '—', eta: '—', node: meta.node || '—', priority: meta.priority || 'normal', error: j.error || null, progress: j.progress || 0, }; }; const refresh = React.useCallback(() => { window.ZAMPP_API.fetch('/jobs') .then(raw => { const norm = (raw || []).map(normalizeJob); window.ZAMPP_DATA.JOBS = norm; setJobs(norm); setLastFetch(Date.now()); }) .catch(() => {}); }, []); React.useEffect(() => { const i = setInterval(refresh, 5000); return () => clearInterval(i); }, [refresh]); const handleRetry = React.useCallback((job) => { window.ZAMPP_API.fetch('/jobs/' + job.id + '/retry', { method: 'POST' }) .then(() => refresh()) .catch(e => alert('Retry failed: ' + e.message)); }, [refresh]); const handleDelete = React.useCallback((job) => { if (!window.confirm('Remove this job from the queue?')) return; window.ZAMPP_API.fetch('/jobs/' + job.id, { method: 'DELETE' }) .then(() => setJobs(prev => prev.filter(j => j.id !== job.id))) .catch(e => alert('Delete failed: ' + e.message)); }, []); const counts = { all: jobs.length, running: jobs.filter(j => j.status === 'running').length, queued: jobs.filter(j => j.status === 'queued').length, done: jobs.filter(j => j.status === 'done').length, failed: jobs.filter(j => j.status === 'failed').length, }; const filtered = tab === 'all' ? jobs : jobs.filter(j => j.status === tab); return (

Jobs

Proxy generation, transcoding, and processing queue
Running
{counts.running}
{counts.queued} queued
Completed
{counts.done}
Total done
Failed
{counts.failed}
0 ? 'var(--warning)' : '' }}> {counts.failed > 0 ? 'Needs attention' : 'All clear'}
Total jobs
{counts.all}
Updated {Math.round((Date.now()-lastFetch)/1000)}s ago
{[ { id: 'all', label: 'All · ' + counts.all }, { id: 'running', label: 'Running · ' + counts.running }, { id: 'queued', label: 'Queued · ' + counts.queued }, { id: 'done', label: 'Done · ' + counts.done }, { id: 'failed', label: 'Failed · ' + counts.failed }, ].map(t => ( ))}
Job
Asset
Node
Progress
ETA
Priority
{filtered.length === 0 ?
No jobs in this category.
: filtered.map(j => )}
); } function JobRow({ job, onRetry, onDelete }) { const iconMap = { Proxy: 'proxy', Transcode: 'film', Thumbnail: 'image', Conform: 'layers' }; return (
{job.kind}
{job.asset}
{job.node}
{job.status === 'running' && (
{Math.round(job.progress)}%
)} {job.status === 'done' && Complete} {job.status === 'queued' && Waiting…} {job.status === 'failed' && {job.error || 'Failed'}}
{job.eta}
{job.priority}
{job.status === 'failed' && ( )} {(job.status === 'queued' || job.status === 'done') && ( )}
); } window.Jobs = Jobs;