From f8bd80e38edc0db8dd901783f26ccc5f1f344275 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Fri, 22 May 2026 08:19:01 -0400 Subject: [PATCH] Add Z-AMPP UI: screens-jobs + screens-editor + modal-new-recorder: screens-jobs.jsx --- services/web-ui/public/screens-jobs.jsx | 158 ++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 services/web-ui/public/screens-jobs.jsx diff --git a/services/web-ui/public/screens-jobs.jsx b/services/web-ui/public/screens-jobs.jsx new file mode 100644 index 0000000..20b9da4 --- /dev/null +++ b/services/web-ui/public/screens-jobs.jsx @@ -0,0 +1,158 @@ +// screens-jobs.jsx — Jobs queue with real-time progress visualization + +const { JOBS: ALL_JOBS } = window.ZAMPP_DATA; + +function Jobs({ navigate }) { + const [tab, setTab] = React.useState("all"); + const [jobs, setJobs] = React.useState(ALL_JOBS); + + 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); + 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, + }; + 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]; + + return ( +
+
+

Jobs

+ Proxy generation, transcoding, AMPP sync — live across worker pool +
+ + +
+ +
+
+
+
Throughput
+
42 / min
+
+18% vs last hour
+ +
+
+
Avg duration
+
4.2 min
+
Proxy: 6.1m · Transcode: 2.4m
+
+
+
Worker pool
+
3 / 4 active
+
GPU util: 67%
+
+
+
Failed (24h)
+
{counts.failed}
+
0 ? "var(--warning)" : "var(--text-3)" }}> + {counts.failed > 0 ? "1 needs attention" : "All good"} +
+
+
+ +
+ {[ + { 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.map(j => )} +
+
+
+ ); +} + +function JobRow({ job }) { + 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} + + )} +
+
{job.eta}
+
+ + {job.priority} + +
+
+ {job.status === "running" && } + {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;