feat: add probe button to SRT/RTMP sources, fix node labels
This commit is contained in:
parent
994fd799d0
commit
bb508d3256
1 changed files with 64 additions and 5 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue