diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 6d4a32262..373f31ad0 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -16,7 +16,6 @@ "ui": "UI", "enrichments": "Enrichments", "cameraManagement": "Management", - "cameraReview": "Review", "masksAndZones": "Masks / Zones", "motionTuner": "Motion Tuner", "triggers": "Triggers", @@ -188,6 +187,10 @@ "testSuccess": "Connection test successful!", "testFailed": "Connection test failed. Please check your input and try again.", "streamDetails": "Stream Details", + "testing": { + "probingMetadata": "Probing camera metadata...", + "fetchingSnapshot": "Fetching camera snapshot..." + }, "warnings": { "noSnapshot": "Unable to fetch a snapshot from the configured stream." }, @@ -197,8 +200,9 @@ "nameLength": "Camera name must be 64 characters or less", "invalidCharacters": "Camera name contains invalid characters", "nameExists": "Camera name already exists", + "customUrlRtspRequired": "Custom URLs must begin with \"rtsp://\". Manual configuration is required for non-RTSP camera streams.", "brands": { - "reolink-rtsp": "Reolink RTSP is not recommended. It is recommended to enable http in the camera settings and restart the camera wizard." + "reolink-rtsp": "Reolink RTSP is not recommended. Enable HTTP in the camera's firmware settings and restart the wizard." } }, "docs": { diff --git a/web/src/components/settings/wizard/Step1NameCamera.tsx b/web/src/components/settings/wizard/Step1NameCamera.tsx index 6ed2339bc..13d426b23 100644 --- a/web/src/components/settings/wizard/Step1NameCamera.tsx +++ b/web/src/components/settings/wizard/Step1NameCamera.tsx @@ -65,6 +65,7 @@ export default function Step1NameCamera({ const { data: config } = useSWR("config"); const [showPassword, setShowPassword] = useState(false); const [isTesting, setIsTesting] = useState(false); + const [testStatus, setTestStatus] = useState(""); const [testResult, setTestResult] = useState(null); const existingCameraNames = useMemo(() => { @@ -88,7 +89,13 @@ export default function Step1NameCamera({ username: z.string().optional(), password: z.string().optional(), brandTemplate: z.enum(CAMERA_BRAND_VALUES).optional(), - customUrl: z.string().optional(), + customUrl: z + .string() + .optional() + .refine( + (val) => !val || val.startsWith("rtsp://"), + t("cameraWizard.step1.errors.customUrlRtspRequired"), + ), }) .refine( (data) => { @@ -204,24 +211,17 @@ export default function Step1NameCamera({ } setIsTesting(true); + setTestStatus(""); setTestResult(null); - // First get probe data for metadata - const probePromise = axios.get("ffprobe", { - params: { paths: streamUrl, detailed: true }, - timeout: 10000, - }); - - // Then get snapshot for preview - const snapshotPromise = axios.get("ffprobe/snapshot", { - params: { url: streamUrl }, - responseType: "blob", - timeout: 10000, - }); - try { // First get probe data for metadata - const probeResponse = await probePromise; + setTestStatus(t("cameraWizard.step1.testing.probingMetadata")); + const probeResponse = await axios.get("ffprobe", { + params: { paths: streamUrl, detailed: true }, + timeout: 10000, + }); + let probeData = null; if ( probeResponse.data && @@ -234,8 +234,13 @@ export default function Step1NameCamera({ // Then get snapshot for preview (only if probe succeeded) let snapshotBlob = null; if (probeData) { + setTestStatus(t("cameraWizard.step1.testing.fetchingSnapshot")); try { - const snapshotResponse = await snapshotPromise; + const snapshotResponse = await axios.get("ffprobe/snapshot", { + params: { url: streamUrl }, + responseType: "blob", + timeout: 10000, + }); snapshotBlob = snapshotResponse.data; } catch (snapshotError) { // Snapshot is optional, don't fail if it doesn't work @@ -321,6 +326,7 @@ export default function Step1NameCamera({ ); } finally { setIsTesting(false); + setTestStatus(""); } }, [form, generateStreamUrl, t]); @@ -610,7 +616,9 @@ export default function Step1NameCamera({ className="flex items-center justify-center gap-2 sm:flex-1" > {isTesting && } - {t("cameraWizard.step1.testConnection")} + {isTesting && testStatus + ? testStatus + : t("cameraWizard.step1.testConnection")} )}