From 6510871448705207ca05e2ad7e5e0ad6bbd57d86 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Fri, 22 May 2026 11:10:01 -0400 Subject: [PATCH] fix: implement real upload (XHR + S3 multipart) and fix SDI recorder device_index + manual fallback: modal-new-recorder.jsx --- services/web-ui/public/modal-new-recorder.jsx | 111 ++++++++++++------ 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/services/web-ui/public/modal-new-recorder.jsx b/services/web-ui/public/modal-new-recorder.jsx index 95a37ef..d54bac5 100644 --- a/services/web-ui/public/modal-new-recorder.jsx +++ b/services/web-ui/public/modal-new-recorder.jsx @@ -1,12 +1,16 @@ // modal-new-recorder.jsx — New Recorder dialog (SRT / RTMP / SDI) function NewRecorderModal({ open, onClose }) { - const { PROJECTS } = window.ZAMPP_DATA; + const { PROJECTS, NODES } = 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 [sdiDeviceIdx, setSdiDeviceIdx] = React.useState(0); + const [sdiNodeId, setSdiNodeId] = React.useState(() => { + const n = window.ZAMPP_DATA.NODES[0]; + return n ? (n.id || n.hostname || '') : ''; + }); const [sdiDevices, setSdiDevices] = React.useState(null); const [recTab, setRecTab] = React.useState('video'); const [proxyTab, setProxyTab] = React.useState('video'); @@ -24,17 +28,28 @@ function NewRecorderModal({ open, onClose }) { const handleCreate = () => { if (!name.trim()) { setSubmitErr('Recorder name is required.'); return; } + if (sourceType === 'SDI' && !sdiNodeId) { setSubmitErr('Select a capture node for SDI.'); 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, }; + + if (sourceType === 'SRT') { + body.source_config = { url: srtUrl }; + } else if (sourceType === 'RTMP') { + body.source_config = { url: rtmpUrl }; + } else { + // SDI: device_index and node_id are top-level fields + body.source_config = {}; + body.device_index = sdiDeviceIdx; + body.node_id = sdiNodeId || undefined; + } + 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'); }); @@ -56,7 +71,8 @@ function NewRecorderModal({ open, onClose }) {
- setName(e.target.value)} /> + setName(e.target.value)} />
@@ -67,11 +83,9 @@ function NewRecorderModal({ open, onClose }) { { id: 'RTMP', label: 'RTMP', desc: 'Real-Time Messaging Protocol', icon: 'globe' }, { id: 'SDI', label: 'SDI', desc: 'Blackmagic DeckLink hardware', icon: 'video' }, ].map(t => ( -
)} {sourceType === 'SDI' && (
- + {sdiDevices === null && ( -
Loading DeckLink devices…
- )} - {sdiDevices !== null && sdiDevices.length === 0 && ( -
- No DeckLink devices found in cluster. Ensure the capture node is online. -
+
Detecting DeckLink devices…
)} {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 => ( - ))}
))}
)} + {sdiDevices !== null && sdiDevices.length === 0 && ( +
+
+ No DeckLink devices auto-detected. Configure manually: +
+
+
+ + +
+
+ + +
+
+
+ )}
)} @@ -228,8 +267,8 @@ function NewRecorderModal({ open, onClose }) {
- setProjectId(e.target.value)} style={{ appearance: 'auto' }}> {PROJECTS.length === 0 ? : PROJECTS.map(p => )} @@ -239,19 +278,17 @@ function NewRecorderModal({ open, onClose }) {
{submitErr && ( -
- {submitErr} -
+
{submitErr}
)}
-