diff --git a/services/web-ui/public/modal-new-recorder.jsx b/services/web-ui/public/modal-new-recorder.jsx
index d54bac5..4508f17 100644
--- a/services/web-ui/public/modal-new-recorder.jsx
+++ b/services/web-ui/public/modal-new-recorder.jsx
@@ -1,5 +1,34 @@
// modal-new-recorder.jsx — New Recorder dialog (SRT / RTMP / SDI)
+function ProbeResult({ result }) {
+ if (!result.ok) {
+ return (
+
+ Probe failed: {result.error}
+
+ );
+ }
+ const d = result.data || {};
+ const entries = Object.entries(d).filter(([, v]) => v !== null && typeof v !== 'object');
+ if (entries.length === 0) {
+ return (
+
+ ✓ Source reachable
+
+ );
+ }
+ return (
+
+ {entries.map(([k, v]) => (
+
+ {k}
+ {String(v)}
+
+ ))}
+
+ );
+}
+
function NewRecorderModal({ open, onClose }) {
const { PROJECTS, NODES } = window.ZAMPP_DATA;
const [name, setName] = React.useState('');
@@ -18,6 +47,8 @@ function NewRecorderModal({ open, onClose }) {
const [projectId, setProjectId] = React.useState(PROJECTS[0]?.id || '');
const [submitting, setSubmitting] = React.useState(false);
const [submitErr, setSubmitErr] = React.useState(null);
+ const [probing, setProbing] = React.useState(false);
+ const [probeResult, setProbeResult] = React.useState(null);
React.useEffect(() => {
if (sourceType !== 'SDI' || sdiDevices !== null) return;
@@ -26,6 +57,19 @@ function NewRecorderModal({ open, onClose }) {
.catch(() => setSdiDevices([]));
}, [sourceType]);
+ React.useEffect(() => { setProbeResult(null); }, [sourceType, srtUrl, rtmpUrl]);
+
+ const handleProbe = () => {
+ setProbing(true);
+ setProbeResult(null);
+ const body = sourceType === 'SRT'
+ ? { source_type: 'srt', url: srtUrl }
+ : { source_type: 'rtmp', url: rtmpUrl };
+ window.ZAMPP_API.fetch('/recorders/probe', { method: 'POST', body: JSON.stringify(body) })
+ .then(r => { setProbing(false); setProbeResult({ ok: true, data: r }); })
+ .catch(e => { setProbing(false); setProbeResult({ ok: false, error: e.message }); });
+ };
+
const handleCreate = () => {
if (!name.trim()) { setSubmitErr('Recorder name is required.'); return; }
if (sourceType === 'SDI' && !sdiNodeId) { setSubmitErr('Select a capture node for SDI.'); return; }
@@ -99,22 +143,36 @@ function NewRecorderModal({ open, onClose }) {
{sourceType === 'SRT' && (
-
setSrtUrl(e.target.value)} />
+
+ setSrtUrl(e.target.value)} style={{ flex: 1 }} />
+
+
Recorder connects out to this URL (caller mode).
+ {probeResult &&
}
)}
{sourceType === 'RTMP' && (
-
setRtmpUrl(e.target.value)} />
+
+ setRtmpUrl(e.target.value)} style={{ flex: 1 }} />
+
+
Recorder pulls this RTMP stream.
+ {probeResult &&
}
)}
@@ -158,7 +216,8 @@ function NewRecorderModal({ open, onClose }) {
?
: NODES.map(n => {
const id = n.id || n.hostname || n.name || '';
- return ;
+ const label = n.hostname || n.name || id;
+ return ;
})}