feat: add probe button to SRT/RTMP sources, fix node labels

This commit is contained in:
Zac Gaetano 2026-05-22 11:33:45 -04:00
parent 994fd799d0
commit bb508d3256

View file

@ -1,5 +1,34 @@
// modal-new-recorder.jsx New Recorder dialog (SRT / RTMP / SDI)
function ProbeResult({ result }) {
if (!result.ok) {
return (
<div style={{ marginTop: 6, padding: '8px 10px', background: 'var(--danger-soft)', border: '1px solid var(--danger)', borderRadius: 5, fontSize: 11.5, color: 'var(--danger)' }}>
Probe failed: {result.error}
</div>
);
}
const d = result.data || {};
const entries = Object.entries(d).filter(([, v]) => v !== null && typeof v !== 'object');
if (entries.length === 0) {
return (
<div style={{ marginTop: 6, padding: '8px 10px', background: 'var(--success-soft)', border: '1px solid var(--success)', borderRadius: 5, fontSize: 11.5, color: 'var(--success)' }}>
Source reachable
</div>
);
}
return (
<div style={{ marginTop: 6, padding: '10px 12px', background: 'var(--bg-2)', border: '1px solid var(--success)', borderRadius: 5, fontSize: 11.5 }}>
{entries.map(([k, v]) => (
<div key={k} style={{ display: 'flex', gap: 8, padding: '2px 0' }}>
<span style={{ color: 'var(--text-3)', minWidth: 100, flexShrink: 0 }}>{k}</span>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 11 }}>{String(v)}</span>
</div>
))}
</div>
);
}
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' && (
<div className="field">
<label className="field-label">Source URL</label>
<input className="field-input mono" placeholder="srt://192.168.1.100:4200"
value={srtUrl} onChange={e => setSrtUrl(e.target.value)} />
<div style={{ display: 'flex', gap: 6 }}>
<input className="field-input mono" placeholder="srt://192.168.1.100:4200"
value={srtUrl} onChange={e => setSrtUrl(e.target.value)} style={{ flex: 1 }} />
<button className="btn ghost sm" onClick={handleProbe} disabled={probing}
style={{ flexShrink: 0, minWidth: 64 }}>
{probing ? '…' : 'Probe'}
</button>
</div>
<div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 4 }}>
Recorder connects out to this URL (caller mode).
</div>
{probeResult && <ProbeResult result={probeResult} />}
</div>
)}
{sourceType === 'RTMP' && (
<div className="field">
<label className="field-label">Source URL</label>
<input className="field-input mono" placeholder="rtmp://server/live/streamkey"
value={rtmpUrl} onChange={e => setRtmpUrl(e.target.value)} />
<div style={{ display: 'flex', gap: 6 }}>
<input className="field-input mono" placeholder="rtmp://server/live/streamkey"
value={rtmpUrl} onChange={e => setRtmpUrl(e.target.value)} style={{ flex: 1 }} />
<button className="btn ghost sm" onClick={handleProbe} disabled={probing}
style={{ flexShrink: 0, minWidth: 64 }}>
{probing ? '…' : 'Probe'}
</button>
</div>
<div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 4 }}>
Recorder pulls this RTMP stream.
</div>
{probeResult && <ProbeResult result={probeResult} />}
</div>
)}
@ -158,7 +216,8 @@ function NewRecorderModal({ open, onClose }) {
? <option value="">No cluster nodes</option>
: NODES.map(n => {
const id = n.id || n.hostname || n.name || '';
return <option key={id} value={id}>{id}</option>;
const label = n.hostname || n.name || id;
return <option key={id} value={id}>{label}</option>;
})}
</select>
</div>