diff --git a/services/web-ui/public/screens-jobs.jsx b/services/web-ui/public/screens-jobs.jsx index 20b9da4..5c84b9c 100644 --- a/services/web-ui/public/screens-jobs.jsx +++ b/services/web-ui/public/screens-jobs.jsx @@ -1,98 +1,106 @@ -// screens-jobs.jsx — Jobs queue with real-time progress visualization - -const { JOBS: ALL_JOBS } = window.ZAMPP_DATA; +// screens-jobs.jsx function Jobs({ navigate }) { - const [tab, setTab] = React.useState("all"); - const [jobs, setJobs] = React.useState(ALL_JOBS); + const [tab, setTab] = React.useState('all'); + const [jobs, setJobs] = React.useState(window.ZAMPP_DATA.JOBS); + const [lastFetch, setLastFetch] = React.useState(Date.now()); + // Poll for job updates every 5s React.useEffect(() => { - const i = setInterval(() => { - setJobs(js => js.map(j => { - if (j.status !== "running") return j; - const next = Math.min(100, j.progress + Math.random() * 3); - if (next >= 100) return { ...j, progress: 100, status: "done", eta: "—" }; - const [m, s] = j.eta.split(":").map(Number); - const total = m * 60 + s - 1; - const newEta = total > 0 ? `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}` : "00:00"; - return { ...j, progress: next, eta: newEta }; - })); - }, 1200); + const poll = () => { + window.ZAMPP_API.fetch('/jobs') + .then(raw => { + const normalized = (raw || []).map(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, + }; + }); + window.ZAMPP_DATA.JOBS = normalized; + setJobs(normalized); + setLastFetch(Date.now()); + }) + .catch(() => {}); + }; + const i = setInterval(poll, 5000); return () => clearInterval(i); }, []); 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, + 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); - - const throughput = [12, 14, 13, 16, 15, 18, 22, 24, 22, 28, 32, 38, 42, 40]; + const filtered = tab === 'all' ? jobs : jobs.filter(j => j.status === tab); return (

Jobs

- Proxy generation, transcoding, AMPP sync — live across worker pool + Proxy generation, transcoding, and processing queue
- - +
-
Throughput
-
42 / min
-
+18% vs last hour
- +
Running
+
{counts.running}
+
{counts.queued} queued
-
Avg duration
-
4.2 min
-
Proxy: 6.1m · Transcode: 2.4m
+
Completed
+
{counts.done}
+
Total done
-
Worker pool
-
3 / 4 active
-
GPU util: 67%
-
-
-
Failed (24h)
+
Failed
{counts.failed}
-
0 ? "var(--warning)" : "var(--text-3)" }}> - {counts.failed > 0 ? "1 needs attention" : "All good"} +
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}` }, + { 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
-
+
Job
Asset
Node
Progress
ETA
Priority
- {filtered.map(j => )} + {filtered.length === 0 + ?
No jobs in this category.
+ : filtered.map(j => )}
@@ -100,59 +108,35 @@ function Jobs({ navigate }) { } function JobRow({ job }) { + const iconMap = { Proxy: 'proxy', Transcode: 'film', Thumbnail: 'image', Conform: 'layers' }; return (
-
- +
+ {job.kind}
-
- {job.asset} -
-
{job.node}
+
{job.asset}
+
{job.node}
- {job.status === "running" && ( + {job.status === 'running' && (
-
-
-
- - {Math.round(job.progress)}% - +
+ {Math.round(job.progress)}%
)} - {job.status === "done" && ( - - Complete - - )} - {job.status === "queued" && ( - Waiting… - )} - {job.status === "failed" && ( - - {job.error} - - )} + {job.status === 'done' && Complete} + {job.status === 'queued' && Waiting…} + {job.status === 'failed' && {job.error || 'Failed'}}
-
{job.eta}
+
{job.eta}
+
{job.priority}
- - {job.priority} - -
-
- {job.status === "running" && } - {job.status === "failed" && } - {(job.status === "queued" || job.status === "done") && } + {job.status === 'failed' && } + {(job.status === 'queued' || job.status === 'done') && }
); } -function iconForJob(kind) { - return { Proxy: "proxy", Transcode: "film", Thumbnail: "image", "AMPP Sync": "refresh" }[kind] || "jobs"; -} - window.Jobs = Jobs;