From 529d14cb6b2b552a5f8c7f14267797508e193144 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Fri, 22 May 2026 10:55:22 -0400 Subject: [PATCH] fix: SDI crash, monitors polling, home RAM fields, editor IN DEV splash, timecode, create recorder API: modal-new-recorder.jsx --- services/web-ui/public/modal-new-recorder.jsx | 201 +++++++++++------- 1 file changed, 121 insertions(+), 80 deletions(-) diff --git a/services/web-ui/public/modal-new-recorder.jsx b/services/web-ui/public/modal-new-recorder.jsx index 3304ddf..95a37ef 100644 --- a/services/web-ui/public/modal-new-recorder.jsx +++ b/services/web-ui/public/modal-new-recorder.jsx @@ -1,14 +1,44 @@ // modal-new-recorder.jsx — New Recorder dialog (SRT / RTMP / SDI) -const { SDI_PORTS_zampp2, PROJECTS: ALL_PROJECTS } = window.ZAMPP_DATA; - function NewRecorderModal({ open, onClose }) { - const [name, setName] = React.useState(""); - const [sourceType, setSourceType] = React.useState("SRT"); + const { PROJECTS } = window.ZAMPP_DATA; + const [name, setName] = React.useState(''); + const [sourceType, setSourceType] = React.useState('SRT'); + const [srtUrl, setSrtUrl] = React.useState('srt://10.0.4.18:4200'); + const [rtmpUrl, setRtmpUrl] = React.useState('rtmp://stream.local/live/cam_a'); const [sdiPort, setSdiPort] = React.useState(1); - const [recTab, setRecTab] = React.useState("video"); - const [proxyTab, setProxyTab] = React.useState("video"); + const [sdiDevices, setSdiDevices] = React.useState(null); + const [recTab, setRecTab] = React.useState('video'); + const [proxyTab, setProxyTab] = React.useState('video'); const [proxyOn, setProxyOn] = React.useState(true); + const [projectId, setProjectId] = React.useState(PROJECTS[0]?.id || ''); + const [submitting, setSubmitting] = React.useState(false); + const [submitErr, setSubmitErr] = React.useState(null); + + React.useEffect(() => { + if (sourceType !== 'SDI' || sdiDevices !== null) return; + window.ZAMPP_API.fetch('/cluster/devices/blackmagic') + .then(d => setSdiDevices(Array.isArray(d) ? d : [])) + .catch(() => setSdiDevices([])); + }, [sourceType]); + + const handleCreate = () => { + if (!name.trim()) { setSubmitErr('Recorder name is required.'); return; } + setSubmitting(true); + setSubmitErr(null); + const body = { + name: name.trim(), + source_type: sourceType.toLowerCase(), + source_config: sourceType === 'SRT' ? { url: srtUrl } + : sourceType === 'RTMP' ? { url: rtmpUrl } + : { port: sdiPort }, + project_id: projectId || undefined, + generate_proxy: proxyOn, + }; + window.ZAMPP_API.fetch('/recorders', { method: 'POST', body: JSON.stringify(body) }) + .then(() => { setSubmitting(false); onClose(); }) + .catch(e => { setSubmitting(false); setSubmitErr(e.message || 'Failed to create recorder'); }); + }; if (!open) return null; @@ -18,7 +48,7 @@ function NewRecorderModal({ open, onClose }) {
New recorder
-
Configure source, codec, and destination
+
Configure source, codec, and destination
@@ -33,61 +63,78 @@ function NewRecorderModal({ open, onClose }) {
{[ - { id: "SRT", label: "SRT", desc: "Secure Reliable Transport — pull caller" }, - { id: "RTMP", label: "RTMP", desc: "Real-Time Messaging Protocol" }, - { id: "SDI", label: "SDI", desc: "Blackmagic DeckLink hardware" }, + { id: 'SRT', label: 'SRT', desc: 'Secure Reliable Transport — pull caller', icon: 'signal' }, + { id: 'RTMP', label: 'RTMP', desc: 'Real-Time Messaging Protocol', icon: 'globe' }, + { id: 'SDI', label: 'SDI', desc: 'Blackmagic DeckLink hardware', icon: 'video' }, ].map(t => ( ))}
- {sourceType === "SRT" && ( + {sourceType === 'SRT' && (
- -
- The recorder connects out to this URL (caller mode). ?mode=caller is appended automatically. + setSrtUrl(e.target.value)} /> +
+ The recorder connects out to this URL (caller mode).
)} - {sourceType === "RTMP" && ( + {sourceType === 'RTMP' && (
- -
- The recorder will pull this RTMP stream. Must be an existing published stream. + setRtmpUrl(e.target.value)} /> +
+ The recorder will pull this RTMP stream.
)} - {sourceType === "SDI" && ( - <> -
- -
- zampp2 · DeckLink Duo 2 · 4 ports - + {sourceType === 'SDI' && ( +
+ + {sdiDevices === null && ( +
Loading DeckLink devices…
+ )} + {sdiDevices !== null && sdiDevices.length === 0 && ( +
+ No DeckLink devices found in cluster. Ensure the capture node is online.
-
-
- - -
- + )} + {sdiDevices !== null && sdiDevices.length > 0 && ( +
+ {sdiDevices.map((dev, di) => ( +
+
{(dev.model || dev.device || 'DECKLINK').toUpperCase()} · {dev.hostname}
+ {(dev.ports || Array.from({ length: dev.port_count || 2 }, (_, i) => ({ idx: i + 1, label: 'SDI ' + (i + 1) }))).map(p => ( + + ))} +
+ ))} +
+ )} +
)}
@@ -95,32 +142,32 @@ function NewRecorderModal({ open, onClose }) { Master recording
- {["video", "audio", "container"].map(t => ( - ))}
- {recTab === "video" && ( -
+ {recTab === 'video' && ( +
)} - {recTab === "audio" && ( -
+ {recTab === 'audio' && ( +
)} - {recTab === "container" && ( -
+ {recTab === 'container' && ( +
@@ -135,8 +182,8 @@ function NewRecorderModal({ open, onClose }) {
Generate proxy
-
- SDI sources record proxy in parallel. Network sources (SRT/RTMP) generate proxy after stop. +
+ SDI sources record proxy in parallel. Network sources generate proxy after stop.
@@ -147,29 +194,29 @@ function NewRecorderModal({ open, onClose }) { Proxy
- {["video", "audio", "container"].map(t => ( - ))}
- {proxyTab === "video" && ( -
+ {proxyTab === 'video' && ( +
)} - {proxyTab === "audio" && ( -
+ {proxyTab === 'audio' && ( +
)} - {proxyTab === "container" && ( + {proxyTab === 'container' && ( )}
@@ -179,42 +226,36 @@ function NewRecorderModal({ open, onClose }) {
Destination
-
- - +
+ +
+ + {submitErr && ( +
+ {submitErr} +
+ )}
- - -
-
-
- ); -} - -function SDIPortMini({ ports, selected, onSelect }) { - return ( -
-
-
DECKLINK DUO 2
-
-
- {ports.map(p => ( - + - ))} +
);