diff --git a/services/web-ui/public/screens-ingest.jsx b/services/web-ui/public/screens-ingest.jsx new file mode 100644 index 0000000..3eaf391 --- /dev/null +++ b/services/web-ui/public/screens-ingest.jsx @@ -0,0 +1,396 @@ +// screens-ingest.jsx — Upload, Recorders, Capture (SDI), Monitors + +const { RECORDERS, NODES, SDI_PORTS_zampp2, PROJECTS } = window.ZAMPP_DATA; + +function Upload({ navigate }) { + const [files, setFiles] = React.useState([ + { id: 1, name: "Drone_Aerial_Lap_4.mov", size: "12.4 GB", progress: 68, status: "uploading" }, + { id: 2, name: "Interview_Director.mxf", size: "2.1 GB", progress: 100, status: "done" }, + { id: 3, name: "Sponsor_Logo_v4.mov", size: "120 MB", progress: 100, status: "done" }, + { id: 4, name: "Pit_Cam_3.mp4", size: "4.8 GB", progress: 24, status: "uploading" }, + { id: 5, name: "Backstage_Audio.wav", size: "920 MB", progress: 0, status: "queued" }, + ]); + + React.useEffect(() => { + const i = setInterval(() => { + setFiles(fs => fs.map(f => { + if (f.status !== "uploading") return f; + const next = f.progress + Math.random() * 4; + if (next >= 100) return { ...f, progress: 100, status: "done" }; + return { ...f, progress: next }; + })); + }, 600); + return () => clearInterval(i); + }, []); + + return ( +
+
+

Upload

+ Drop video, audio, or stills — we proxy and index automatically. +
+
+
+
+ +
Protour 2026
+
+
+ +
Master files
+
+
+ +
+ +
Drop files here or click to browse
+
Video, audio, and image files — up to 5 GB each
+
+ {["MOV", "MP4", "MXF", "ProRes", "DNxHR", "WAV", "AIFF"].map(f => {f})} +
+
+ +
+
+ Queue + {files.length} + + + +
+
+ {files.map(f => ( +
+ +
+
+ {f.name} + {f.size} +
+
+
+
+
+ + {f.status === "done" ? "✓ done" : f.status === "queued" ? "queued" : `${Math.round(f.progress)}%`} + +
+ ))} +
+
+
+
+ ); +} + +function Recorders({ navigate, onNew }) { + return ( +
+
+

Recorders

+ Live ingest from SRT, RTMP, and SDI sources +
+
+ + 4 recording · 1 armed · 1 error +
+ +
+
+
+ {RECORDERS.map(r => )} +
+
+
+ ); +} + +function RecorderRow({ recorder }) { + const isRec = recorder.status === "recording"; + return ( +
+
+ {recorder.audio ? ( +
+ + +
+ ) : isRec ? ( + + ) : ( +
+ +
+ )} +
+
+
+ {recorder.name} + + {recorder.status.toUpperCase()} + + {recorder.source} +
+
{recorder.url}
+
+ {recorder.codec}· + {recorder.res}· + node: {recorder.node} +
+
+
+
+
Elapsed
+
{recorder.elapsed}
+
+
+
Bitrate
+
{recorder.bitrate}
+
+
+
Health
+
+ +
+
+
+
+ {isRec ? ( + + ) : recorder.status === "error" ? ( + + ) : ( + + )} + +
+
+ ); +} + +function HealthBar({ value }) { + const color = value > 80 ? "var(--success)" : value > 40 ? "var(--warning)" : "var(--danger)"; + return ( +
+
+
+
+ {value}% +
+ ); +} + +function badgeForStatus(s) { + return { recording: "live", armed: "accent", idle: "neutral", error: "danger", offline: "neutral" }[s] || "neutral"; +} + +function Capture({ navigate }) { + const [activePort, setActivePort] = React.useState(1); + const ports = SDI_PORTS_zampp2; + + return ( +
+
+

Capture

+ DeckLink SDI ingest — multi-port routing across cluster nodes +
+ + +
+
+
+ + +
+
+
+ ); +} + +function DeckLinkVisual({ ports, activePort, onSelect }) { + return ( +
+
+
+
DeckLink Duo 2
+
zampp2 · 172.18.91.217
+
+ ONLINE +
+
+
+
DECKLINK DUO 2
+
+
+
+
+
+
+ {ports.map(p => ( + + ))} +
+
+
+ Ports: 4 + Active: {ports.filter(p => p.active).length} + Recording: {ports.filter(p => p.recording).length} +
+
+ ); +} + +function CaptureDetail({ port }) { + if (!port.active) { + return ( +
+ +
No signal on SDI {port.idx}
+
Connect a source, then click Refresh.
+
+ ); + } + return ( +
+
+ +
+ {port.recording && ( +
+ REC +
+ )} +
+ + +
+
+ {port.signal} +
+
+
+ + + + + + +
+
+ {!port.recording ? ( + + ) : ( + + )} + + +
+
+ ); +} + +function CaptureStat({ label, value }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +function Monitors({ navigate }) { + const [grid, setGrid] = React.useState(4); + const allFeeds = [ + ...RECORDERS.filter(r => !r.audio).map(r => ({ ...r, kind: "video" })), + { id: "audio1", name: "FOH Mix", kind: "audio" }, + { id: "audio2", name: "Stage Mics", kind: "audio" }, + { id: "audio3", name: "PGM Bus", kind: "audio" }, + ]; + const feeds = allFeeds.slice(0, grid * grid - (grid === 2 ? 1 : 0)); + const gridSize = grid; + + return ( +
+
+

Monitors

+ Multi-cam live monitoring across all active feeds +
+
+ {[2, 3, 4].map(n => ( + + ))} +
+ +
+
+
+ {feeds.map((f, i) => ( + + ))} +
+
+
+ ); +} + +function MonitorTile({ feed, seed }) { + if (feed.kind === "audio") { + return ( +
+
+ +
+
+ + +
+
+ LIVE + {feed.name} +
+
+ ); + } + return ( +
+ +
+
+ {feed.status === "recording" && REC} + {feed.status === "armed" && ARMED} + {feed.status === "error" && ERR} + {feed.status === "idle" && IDLE} +
+
+ {feed.name} + {feed.elapsed && feed.elapsed !== "00:00:00" && {feed.elapsed}} +
+
+ ); +} + +Object.assign(window, { Upload, Recorders, Capture, Monitors });