diff --git a/deploy/api-smoke.sh b/deploy/api-smoke.sh new file mode 100755 index 0000000..5a205e4 --- /dev/null +++ b/deploy/api-smoke.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# Dragonflight MAM API smoke test +# +# Hits every read-only endpoint and a handful of safe write endpoints +# against a running mam-api. Reports per-endpoint HTTP code + a one-line +# pass/fail. Exits non-zero on any failure. +# +# Usage: +# deploy/api-smoke.sh # against http://localhost:47432 +# API=http://10.0.0.25:47432 deploy/api-smoke.sh + +set -u + +API="${API:-http://localhost:47432}" +PASS=0 +FAIL=0 + +# Per-endpoint check. Args: METHOD PATH EXPECTED_HTTP_CODE [BODY] +# Treats anything < 500 as OK by default; auth-gated endpoints typically +# return 401 with AUTH_ENABLED, also acceptable. +hit() { + local method="$1" path="$2" expect="${3:-2..}" body="${4:-}" + local args=(-s -o /dev/null -w '%{http_code}' -X "$method" "${API}${path}") + if [ -n "$body" ]; then args+=(-H 'Content-Type: application/json' -d "$body"); fi + local code + code=$(curl "${args[@]}" 2>/dev/null || echo "000") + + if [[ "$code" =~ ^(2|3|401|400)[0-9][0-9]$ ]]; then + printf " %s %-40s %s OK\n" "$method" "$path" "$code" + PASS=$((PASS + 1)) + else + printf " %s %-40s %s FAIL\n" "$method" "$path" "$code" + FAIL=$((FAIL + 1)) + fi +} + +echo "Dragonflight API smoke test — target ${API}" + +echo "" +echo "── auth ──────────────────────────────────────────" +hit GET /api/v1/auth/me + +echo "" +echo "── core lists ────────────────────────────────────" +hit GET /api/v1/projects +hit GET /api/v1/assets +hit GET /api/v1/assets?limit=5 +hit GET /api/v1/recorders +hit GET /api/v1/jobs +hit GET /api/v1/bins +hit GET /api/v1/users +hit GET /api/v1/groups +hit GET /api/v1/cluster +hit GET /api/v1/cluster/containers +hit GET /api/v1/cluster/devices/blackmagic + +echo "" +echo "── settings ──────────────────────────────────────" +hit GET /api/v1/settings/s3 +hit GET /api/v1/settings/transcoding +hit GET /api/v1/settings/growing +hit GET /api/v1/settings/ampp +hit GET /api/v1/settings/hardware +hit GET /api/v1/settings/capture-service + +echo "" +echo "── feature endpoints ─────────────────────────────" +hit GET /api/v1/metrics/home +hit GET /api/v1/metrics/home?hours=1 +hit GET /api/v1/schedules +hit GET /api/v1/schedules?status=upcoming +hit GET /api/v1/sdk + +echo "" +echo "── deep-link sanity (one asset) ──────────────────" +ASSET_ID=$(curl -s "${API}/api/v1/assets?limit=1" 2>/dev/null \ + | sed -n 's/.*"id":"\([0-9a-f-]\{36\}\)".*/\1/p' | head -1) +if [ -n "$ASSET_ID" ]; then + echo " using asset_id=$ASSET_ID" + hit GET "/api/v1/assets/$ASSET_ID" + hit GET "/api/v1/assets/$ASSET_ID/comments" + hit GET "/api/v1/assets/$ASSET_ID/stream" + hit GET "/api/v1/assets/$ASSET_ID/thumbnail" +else + echo " (no assets to deep-link; skipping per-asset endpoints)" +fi + +echo "" +echo "── deep-link sanity (one recorder) ───────────────" +REC_ID=$(curl -s "${API}/api/v1/recorders" 2>/dev/null \ + | sed -n 's/.*"id":"\([0-9a-f-]\{36\}\)".*/\1/p' | head -1) +if [ -n "$REC_ID" ]; then + echo " using recorder_id=$REC_ID" + hit GET "/api/v1/recorders/$REC_ID" + hit GET "/api/v1/recorders/$REC_ID/status" +else + echo " (no recorders to deep-link)" +fi + +echo "" +echo "── summary ───────────────────────────────────────" +echo " PASS: $PASS" +echo " FAIL: $FAIL" +[ "$FAIL" -eq 0 ] diff --git a/services/web-ui/public/modal-new-recorder.jsx b/services/web-ui/public/modal-new-recorder.jsx index 560bc6a..80fbd47 100644 --- a/services/web-ui/public/modal-new-recorder.jsx +++ b/services/web-ui/public/modal-new-recorder.jsx @@ -30,14 +30,15 @@ function ProbeResult({ result }) { } function NewRecorderModal({ open, onClose }) { - const { PROJECTS, NODES } = window.ZAMPP_DATA; + const PROJECTS = window.ZAMPP_DATA?.PROJECTS || []; + const NODES = window.ZAMPP_DATA?.NODES || []; 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 [sdiDeviceIdx, setSdiDeviceIdx] = React.useState(0); const [sdiNodeId, setSdiNodeId] = React.useState(() => { - const n = window.ZAMPP_DATA.NODES[0]; + const n = NODES[0]; return n ? (n.id || n.hostname || '') : ''; }); const [sdiDevices, setSdiDevices] = React.useState(null);