mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-11 09:37:37 +03:00
match onvif probe pane with other steps in the wizard
This commit is contained in:
parent
6bffeffd1c
commit
a05c6bd6fe
@ -204,12 +204,21 @@
|
|||||||
"detectionMethodDescription": "Probe the camera with ONVIF (if supported) to find camera stream URLs, or manually select the camera brand to use pre-defined URLs. To enter a custom RTSP URL, choose the manual method and select \"Other\".",
|
"detectionMethodDescription": "Probe the camera with ONVIF (if supported) to find camera stream URLs, or manually select the camera brand to use pre-defined URLs. To enter a custom RTSP URL, choose the manual method and select \"Other\".",
|
||||||
"onvifPortDescription": "For cameras that support ONVIF, this is usually 80 or 8080.",
|
"onvifPortDescription": "For cameras that support ONVIF, this is usually 80 or 8080.",
|
||||||
"probingDevice": "Probing device...",
|
"probingDevice": "Probing device...",
|
||||||
|
"probeSuccessful": "Probe successful",
|
||||||
"probeError": "Probe Error",
|
"probeError": "Probe Error",
|
||||||
"probeNoSuccess": "Probe unsuccessful",
|
"probeNoSuccess": "Probe unsuccessful",
|
||||||
|
"manufacturer": "Manufacturer",
|
||||||
|
"model": "Model",
|
||||||
|
"firmware": "Firmware",
|
||||||
|
"profiles": "Profiles",
|
||||||
|
"ptzSupport": "PTZ Support",
|
||||||
|
"autotrackingSupport": "Autotracking Support",
|
||||||
|
"presets": "Presets",
|
||||||
"deviceInfo": "Device Information",
|
"deviceInfo": "Device Information",
|
||||||
"rtspCandidates": "RTSP Candidates",
|
"rtspCandidates": "RTSP Candidates",
|
||||||
|
"candidateStreamTitle": "Candidate {{number}}",
|
||||||
"useCandidate": "Use",
|
"useCandidate": "Use",
|
||||||
"uriCopied": "URI copied",
|
"uriCopy": "Copy",
|
||||||
"probeFailed": "Failed to probe camera: {{error}}",
|
"probeFailed": "Failed to probe camera: {{error}}",
|
||||||
"probeButton": "Probe camera",
|
"probeButton": "Probe camera",
|
||||||
"warnings": {
|
"warnings": {
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Card, CardContent, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardTitle } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
// Input removed: URL shown as text
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
import { FaExclamationCircle, FaCopy, FaCheck } from "react-icons/fa";
|
import { FaCopy, FaCheck } from "react-icons/fa";
|
||||||
|
import { LuX } from "react-icons/lu";
|
||||||
|
import { CiCircleAlert } from "react-icons/ci";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import type {
|
import type {
|
||||||
@ -11,6 +15,8 @@ import type {
|
|||||||
TestResult,
|
TestResult,
|
||||||
CandidateTestMap,
|
CandidateTestMap,
|
||||||
} from "@/types/cameraWizard";
|
} from "@/types/cameraWizard";
|
||||||
|
import { FaCircleCheck } from "react-icons/fa6";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type OnvifProbeResultsProps = {
|
type OnvifProbeResultsProps = {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@ -61,15 +67,11 @@ export default function OnvifProbeResults({
|
|||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-start gap-3 rounded-lg border border-destructive/50 bg-destructive/10 p-4">
|
<Alert variant="destructive">
|
||||||
<FaExclamationCircle className="mt-1 size-5 flex-shrink-0 text-destructive" />
|
<CiCircleAlert className="size-5" />
|
||||||
<div className="space-y-1">
|
<AlertTitle>{t("cameraWizard.step1.probeError")}</AlertTitle>
|
||||||
<h3 className="font-medium text-destructive">
|
{error && <AlertDescription>{error}</AlertDescription>}
|
||||||
{t("cameraWizard.step1.probeError")}
|
</Alert>
|
||||||
</h3>
|
|
||||||
{error && <p className="text-sm text-muted-foreground">{error}</p>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button onClick={onRetry} variant="outline" className="w-full">
|
<Button onClick={onRetry} variant="outline" className="w-full">
|
||||||
{t("button.retry", { ns: "common" })}
|
{t("button.retry", { ns: "common" })}
|
||||||
</Button>
|
</Button>
|
||||||
@ -80,19 +82,13 @@ export default function OnvifProbeResults({
|
|||||||
if (!probeResult?.success) {
|
if (!probeResult?.success) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-start gap-3 rounded-lg border border-amber-500/50 bg-amber-500/10 p-4">
|
<Alert variant="destructive">
|
||||||
<FaExclamationCircle className="mt-1 size-5 flex-shrink-0 text-amber-600" />
|
<CiCircleAlert className="size-5" />
|
||||||
<div className="space-y-1">
|
<AlertTitle>{t("cameraWizard.step1.probeNoSuccess")}</AlertTitle>
|
||||||
<h3 className="font-medium text-amber-900">
|
{probeResult?.message && (
|
||||||
{t("cameraWizard.step1.probeNoSuccess")}
|
<AlertDescription>{probeResult.message}</AlertDescription>
|
||||||
</h3>
|
)}
|
||||||
{probeResult?.message && (
|
</Alert>
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{probeResult.message}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button onClick={onRetry} variant="outline" className="w-full">
|
<Button onClick={onRetry} variant="outline" className="w-full">
|
||||||
{t("button.retry", { ns: "common" })}
|
{t("button.retry", { ns: "common" })}
|
||||||
</Button>
|
</Button>
|
||||||
@ -104,6 +100,13 @@ export default function OnvifProbeResults({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{/* Probe success header (green check + text) */}
|
||||||
|
{probeResult?.success && (
|
||||||
|
<div className="mb-3 flex flex-row items-center gap-2 text-sm text-success">
|
||||||
|
<FaCircleCheck className="size-4" />
|
||||||
|
<span>{t("cameraWizard.step1.probeSuccessful")}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Card>
|
<Card>
|
||||||
<CardTitle className="border-b p-4 text-sm">
|
<CardTitle className="border-b p-4 text-sm">
|
||||||
{t("cameraWizard.step1.deviceInfo")}
|
{t("cameraWizard.step1.deviceInfo")}
|
||||||
@ -111,49 +114,73 @@ export default function OnvifProbeResults({
|
|||||||
<CardContent className="space-y-2 p-4 text-sm">
|
<CardContent className="space-y-2 p-4 text-sm">
|
||||||
{probeResult.manufacturer && (
|
{probeResult.manufacturer && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Manufacturer:</span>{" "}
|
<span className="text-muted-foreground">
|
||||||
<span className="font-medium">{probeResult.manufacturer}</span>
|
{t("cameraWizard.step1.manufacturer")}:
|
||||||
|
</span>{" "}
|
||||||
|
<span className="text-primary-variant">
|
||||||
|
{probeResult.manufacturer}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{probeResult.model && (
|
{probeResult.model && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Model:</span>{" "}
|
<span className="text-muted-foreground">
|
||||||
<span className="font-medium">{probeResult.model}</span>
|
{t("cameraWizard.step1.model")}:
|
||||||
|
</span>{" "}
|
||||||
|
<span className="text-primary-variant">{probeResult.model}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{probeResult.firmware_version && (
|
{probeResult.firmware_version && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Firmware:</span>{" "}
|
<span className="text-muted-foreground">
|
||||||
<span className="font-medium">
|
{t("cameraWizard.step1.firmware")}:
|
||||||
|
</span>{" "}
|
||||||
|
<span className="text-primary-variant">
|
||||||
{probeResult.firmware_version}
|
{probeResult.firmware_version}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{probeResult.profiles_count !== undefined && (
|
{probeResult.profiles_count !== undefined && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Profiles:</span>{" "}
|
<span className="text-muted-foreground">
|
||||||
<span className="font-medium">{probeResult.profiles_count}</span>
|
{t("cameraWizard.step1.profiles")}:
|
||||||
|
</span>{" "}
|
||||||
|
<span className="text-primary-variant">
|
||||||
|
{probeResult.profiles_count}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{probeResult.ptz_supported !== undefined && (
|
{probeResult.ptz_supported !== undefined && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">PTZ Support:</span>{" "}
|
<span className="text-muted-foreground">
|
||||||
<span className="font-medium">
|
{t("cameraWizard.step1.ptzSupport")}:
|
||||||
{probeResult.ptz_supported ? "Yes" : "No"}
|
</span>{" "}
|
||||||
|
<span className="text-primary-variant">
|
||||||
|
{probeResult.ptz_supported
|
||||||
|
? t("yes", { ns: "common" })
|
||||||
|
: t("no", { ns: "common" })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{probeResult.ptz_supported && probeResult.autotrack_supported && (
|
{probeResult.ptz_supported && probeResult.autotrack_supported && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Autotrack Support:</span>{" "}
|
<span className="text-muted-foreground">
|
||||||
<span className="font-medium">Yes</span>
|
{t("cameraWizard.step1.autotrackingSupport")}:
|
||||||
|
</span>{" "}
|
||||||
|
<span className="text-primary-variant">
|
||||||
|
{t("yes", { ns: "common" })}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{probeResult.ptz_supported &&
|
{probeResult.ptz_supported &&
|
||||||
probeResult.presets_count !== undefined && (
|
probeResult.presets_count !== undefined && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Presets:</span>{" "}
|
<span className="text-muted-foreground">
|
||||||
<span className="font-medium">{probeResult.presets_count}</span>
|
{t("cameraWizard.step1.presets")}:
|
||||||
|
</span>{" "}
|
||||||
|
<span className="text-primary-variant">
|
||||||
|
{probeResult.presets_count}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@ -161,9 +188,7 @@ export default function OnvifProbeResults({
|
|||||||
|
|
||||||
{rtspCandidates.length > 0 && (
|
{rtspCandidates.length > 0 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-sm font-medium">
|
<h3 className="text-sm">{t("cameraWizard.step1.rtspCandidates")}</h3>
|
||||||
{t("cameraWizard.step1.rtspCandidates")}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{rtspCandidates.map((candidate, idx) => {
|
{rtspCandidates.map((candidate, idx) => {
|
||||||
@ -174,12 +199,13 @@ export default function OnvifProbeResults({
|
|||||||
return (
|
return (
|
||||||
<CandidateItem
|
<CandidateItem
|
||||||
key={idx}
|
key={idx}
|
||||||
|
index={idx}
|
||||||
candidate={candidate}
|
candidate={candidate}
|
||||||
copiedUri={copiedUri}
|
copiedUri={copiedUri}
|
||||||
onCopy={() => handleCopyUri(candidate.uri)}
|
onCopy={() => handleCopyUri(candidate.uri)}
|
||||||
onUse={() => onSelectCandidate(candidate.uri)}
|
onUse={() => onSelectCandidate(candidate.uri)}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onTest={() => testCandidate && testCandidate(candidate.uri)}
|
testCandidate={testCandidate}
|
||||||
candidateTest={candidateTest}
|
candidateTest={candidateTest}
|
||||||
isTesting={isTesting}
|
isTesting={isTesting}
|
||||||
/>
|
/>
|
||||||
@ -194,129 +220,142 @@ export default function OnvifProbeResults({
|
|||||||
|
|
||||||
type CandidateItemProps = {
|
type CandidateItemProps = {
|
||||||
candidate: OnvifRtspCandidate;
|
candidate: OnvifRtspCandidate;
|
||||||
isTested?: boolean;
|
index?: number;
|
||||||
|
// isTested?: boolean; (unused)
|
||||||
copiedUri: string | null;
|
copiedUri: string | null;
|
||||||
onCopy: () => void;
|
onCopy: () => void;
|
||||||
onUse: () => void;
|
onUse: () => void;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
onTest?: () => void;
|
// onTest?: () => void; (unused)
|
||||||
|
testCandidate?: (uri: string) => void;
|
||||||
candidateTest?: TestResult | { success: false; error: string };
|
candidateTest?: TestResult | { success: false; error: string };
|
||||||
isTesting?: boolean;
|
isTesting?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function CandidateItem({
|
function CandidateItem({
|
||||||
|
index,
|
||||||
candidate,
|
candidate,
|
||||||
isTested,
|
|
||||||
copiedUri,
|
copiedUri,
|
||||||
onCopy,
|
onCopy,
|
||||||
onUse,
|
onUse,
|
||||||
isSelected,
|
isSelected,
|
||||||
onTest,
|
testCandidate,
|
||||||
candidateTest,
|
candidateTest,
|
||||||
isTesting,
|
isTesting,
|
||||||
}: CandidateItemProps) {
|
}: CandidateItemProps) {
|
||||||
const { t } = useTranslation(["views/settings"]);
|
const { t } = useTranslation(["views/settings"]);
|
||||||
const [showFull, setShowFull] = useState(false);
|
const [showFull, setShowFull] = useState(false);
|
||||||
|
|
||||||
// Mask credentials for display
|
|
||||||
const maskUri = (uri: string) => {
|
const maskUri = (uri: string) => {
|
||||||
const match = uri.match(/rtsp:\/\/([^:]+):([^@]+)@(.+)/);
|
const match = uri.match(/rtsp:\/\/([^:]+):([^@]+)@(.+)/);
|
||||||
if (match) {
|
if (match) return `rtsp://${match[1]}:••••@${match[3]}`;
|
||||||
return `rtsp://${match[1]}:••••@${match[3]}`;
|
|
||||||
}
|
|
||||||
return uri;
|
return uri;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`rounded-lg border p-3 ${
|
className={cn(
|
||||||
isSelected ? "border-selected bg-selected/10" : "border-input bg-card"
|
"rounded-lg bg-card",
|
||||||
}`}
|
isSelected &&
|
||||||
|
"outline outline-[3px] -outline-offset-[2.8px] outline-selected duration-200",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="space-y-2">
|
<div className="flex flex-col space-y-4 p-4">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between">
|
||||||
<div className="min-w-0 flex-1">
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<h4 className="font-medium">
|
||||||
{isTested !== undefined && (
|
{t("cameraWizard.step1.candidateStreamTitle", {
|
||||||
<span
|
number: (index ?? 0) + 1,
|
||||||
className={`text-xs font-medium ${
|
})}
|
||||||
isTested ? "text-green-600" : "text-red-600"
|
</h4>
|
||||||
}`}
|
{candidateTest?.success && (
|
||||||
>
|
<div className="mt-1 text-sm text-muted-foreground">
|
||||||
{isTested ? "✓ OK" : "✗ Failed"}
|
{[
|
||||||
</span>
|
candidateTest.resolution,
|
||||||
)}
|
candidateTest.fps
|
||||||
</div>
|
? `${candidateTest.fps} ${t(
|
||||||
<div className="mt-1 flex items-center gap-2">
|
"cameraWizard.testResultLabels.fps",
|
||||||
<p
|
)}`
|
||||||
className="cursor-pointer break-all text-xs text-muted-foreground hover:underline"
|
: null,
|
||||||
onClick={() => setShowFull(!showFull)}
|
candidateTest.videoCodec,
|
||||||
title="Click to toggle masked/full view"
|
candidateTest.audioCodec,
|
||||||
>
|
]
|
||||||
{showFull ? candidate.uri : maskUri(candidate.uri)}
|
.filter(Boolean)
|
||||||
</p>
|
.join(" · ")}
|
||||||
<Button
|
</div>
|
||||||
size="sm"
|
)}
|
||||||
variant="ghost"
|
|
||||||
onClick={onCopy}
|
|
||||||
className="h-7 w-7 p-0"
|
|
||||||
title="Copy URI"
|
|
||||||
>
|
|
||||||
{copiedUri === candidate.uri ? (
|
|
||||||
<FaCheck className="size-3" />
|
|
||||||
) : (
|
|
||||||
<FaCopy className="size-3" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-shrink-0 items-center gap-2">
|
<div className="flex flex-shrink-0 items-center gap-2">
|
||||||
|
{candidateTest?.success && (
|
||||||
|
<div className="flex items-center gap-2 text-sm">
|
||||||
|
<FaCircleCheck className="size-4 text-success" />
|
||||||
|
<span className="text-success">
|
||||||
|
{t("cameraWizard.step2.connected")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{candidateTest && !candidateTest.success && (
|
||||||
|
<div className="flex items-center gap-2 text-sm">
|
||||||
|
<LuX className="size-4 text-danger" />
|
||||||
|
<span className="text-danger">
|
||||||
|
{t("cameraWizard.step2.notConnected")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-1 flex items-start gap-2">
|
||||||
|
<p
|
||||||
|
className="flex-1 cursor-pointer break-all text-sm text-primary-variant hover:underline"
|
||||||
|
onClick={() => setShowFull((s) => !s)}
|
||||||
|
title={t("cameraWizard.step1.toggleUriView")}
|
||||||
|
>
|
||||||
|
{showFull ? candidate.uri : maskUri(candidate.uri)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={onCopy}
|
||||||
|
className="mr-4 size-8 p-0"
|
||||||
|
title={t("cameraWizard.step1.uriCopy")}
|
||||||
|
>
|
||||||
|
{copiedUri === candidate.uri ? (
|
||||||
|
<FaCheck className="size-3" />
|
||||||
|
) : (
|
||||||
|
<FaCopy className="size-3" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onTest}
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-7 px-3 text-xs"
|
onClick={() => testCandidate?.(candidate.uri)}
|
||||||
|
className="h-8 px-3 text-sm"
|
||||||
>
|
>
|
||||||
{isTesting ? (
|
{isTesting ? (
|
||||||
<ActivityIndicator className="size-3" />
|
<ActivityIndicator className="size-3" />
|
||||||
) : (
|
) : (
|
||||||
t("cameraWizard.step1.test")
|
t("cameraWizard.step1.testConnection")
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
onClick={onUse}
|
|
||||||
variant="default"
|
|
||||||
className="h-7 px-3 text-xs"
|
|
||||||
>
|
|
||||||
{t("cameraWizard.step1.useCandidate")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{candidateTest && candidateTest.success && (
|
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="mt-3 flex flex-row justify-end">
|
||||||
{candidateTest.resolution && (
|
<Button
|
||||||
<span className="mr-2">
|
size="sm"
|
||||||
{`${t("cameraWizard.testResultLabels.resolution")} ${candidateTest.resolution}`}
|
onClick={onUse}
|
||||||
</span>
|
variant="select"
|
||||||
)}
|
className="h-8 px-3 text-sm"
|
||||||
{candidateTest.fps && (
|
>
|
||||||
<span className="mr-2">
|
{t("cameraWizard.step1.useCandidate")}
|
||||||
{t("cameraWizard.testResultLabels.fps")} {candidateTest.fps}
|
</Button>
|
||||||
</span>
|
</div>
|
||||||
)}
|
|
||||||
{candidateTest.videoCodec && (
|
|
||||||
<span className="mr-2">
|
|
||||||
{`${t("cameraWizard.testResultLabels.video")} ${candidateTest.videoCodec}`}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{candidateTest.audioCodec && (
|
|
||||||
<span className="mr-2">
|
|
||||||
{`${t("cameraWizard.testResultLabels.audio")} ${candidateTest.audioCodec}`}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -458,7 +458,13 @@ export default function Step1NameCamera({
|
|||||||
|
|
||||||
if (streamConfigs.length > 0) {
|
if (streamConfigs.length > 0) {
|
||||||
// Add all successful streams and navigate to Step 2
|
// Add all successful streams and navigate to Step 2
|
||||||
onNext({ streams: streamConfigs, customUrl: firstSuccessfulUri });
|
const values = form.getValues();
|
||||||
|
const cameraName = values.cameraName;
|
||||||
|
onNext({
|
||||||
|
cameraName,
|
||||||
|
streams: streamConfigs,
|
||||||
|
customUrl: firstSuccessfulUri,
|
||||||
|
});
|
||||||
toast.success(t("cameraWizard.step1.testSuccess"));
|
toast.success(t("cameraWizard.step1.testSuccess"));
|
||||||
setProbeDialogOpen(false);
|
setProbeDialogOpen(false);
|
||||||
setProbeResult(null);
|
setProbeResult(null);
|
||||||
@ -490,7 +496,7 @@ export default function Step1NameCamera({
|
|||||||
setIsTesting(false);
|
setIsTesting(false);
|
||||||
setTestStatus("");
|
setTestStatus("");
|
||||||
}
|
}
|
||||||
}, [selectedCandidateUris, t, onNext, probeUri]);
|
}, [selectedCandidateUris, t, onNext, probeUri, form]);
|
||||||
|
|
||||||
const testCandidate = useCallback(
|
const testCandidate = useCallback(
|
||||||
async (uri: string) => {
|
async (uri: string) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user