+ {probeResult?.success && (
+
+
+ {t("cameraWizard.step2.probeSuccessful")}
+
+ )}
+
{t("cameraWizard.step2.deviceInfo")}
+
+
+ {probeResult.manufacturer && (
- {t("cameraWizard.step2.presets")}:
+ {t("cameraWizard.step2.manufacturer")}:
{" "}
- {probeResult.presets_count}
+ {probeResult.manufacturer}
)}
-
-
+ {probeResult.model && (
+
+
+ {t("cameraWizard.step2.model")}:
+ {" "}
+
+ {probeResult.model}
+
+
+ )}
+ {probeResult.firmware_version && (
+
+
+ {t("cameraWizard.step2.firmware")}:
+ {" "}
+
+ {probeResult.firmware_version}
+
+
+ )}
+ {probeResult.profiles_count !== undefined && (
+
+
+ {t("cameraWizard.step2.profiles")}:
+ {" "}
+
+ {probeResult.profiles_count}
+
+
+ )}
+ {probeResult.ptz_supported !== undefined && (
+
+
+ {t("cameraWizard.step2.ptzSupport")}:
+ {" "}
+
+ {probeResult.ptz_supported
+ ? t("yes", { ns: "common" })
+ : t("no", { ns: "common" })}
+
+
+ )}
+ {probeResult.ptz_supported && probeResult.autotrack_supported && (
+
+
+ {t("cameraWizard.step2.autotrackingSupport")}:
+ {" "}
+
+ {t("yes", { ns: "common" })}
+
+
+ )}
+ {probeResult.ptz_supported &&
+ probeResult.presets_count !== undefined && (
+
+
+ {t("cameraWizard.step2.presets")}:
+ {" "}
+
+ {probeResult.presets_count}
+
+
+ )}
+
+
+
+
+ {rtspCandidates.length > 0 && (
+
+
+ {t("cameraWizard.step2.rtspCandidates")}
+
+
+ {t("cameraWizard.step2.rtspCandidatesDescription")}
+
- {rtspCandidates.length > 0 && (
-
-
{t("cameraWizard.step2.rtspCandidates")}
+
+ {rtspCandidates.map((candidate, idx) => {
+ const isSelected = !!selectedUris?.includes(candidate.uri);
+ const candidateTest = candidateTests?.[candidate.uri];
+ const isTesting = testingCandidates?.[candidate.uri];
-
- {rtspCandidates.map((candidate, idx) => {
- const isSelected = !!selectedUris?.includes(candidate.uri);
- const candidateTest = candidateTests?.[candidate.uri];
- const isTesting = testingCandidates?.[candidate.uri];
-
- return (
- handleCopyUri(candidate.uri)}
- onUse={() => onSelectCandidate(candidate.uri)}
- isSelected={isSelected}
- testCandidate={testCandidate}
- candidateTest={candidateTest}
- isTesting={isTesting}
- />
- );
- })}
+ return (
+ handleCopyUri(candidate.uri)}
+ onUse={() => onSelectCandidate(candidate.uri)}
+ isSelected={isSelected}
+ testCandidate={testCandidate}
+ candidateTest={candidateTest}
+ isTesting={isTesting}
+ />
+ );
+ })}
+
-
- )}
-
+ )}
+
+ >
);
}
type CandidateItemProps = {
candidate: OnvifRtspCandidate;
index?: number;
- // isTested?: boolean; (unused)
copiedUri: string | null;
onCopy: () => void;
onUse: () => void;
isSelected?: boolean;
- // onTest?: () => void; (unused)
testCandidate?: (uri: string) => void;
candidateTest?: TestResult | { success: false; error: string };
isTesting?: boolean;
@@ -253,110 +270,115 @@ function CandidateItem({
};
return (
-
-
-
-
-
- {t("cameraWizard.step2.candidateStreamTitle", {
- number: (index ?? 0) + 1,
- })}
-
- {candidateTest?.success && (
-
- {[
- candidateTest.resolution,
- candidateTest.fps
- ? `${candidateTest.fps} ${t(
- "cameraWizard.testResultLabels.fps",
- )}`
- : null,
- candidateTest.videoCodec,
- candidateTest.audioCodec,
- ]
- .filter(Boolean)
- .join(" · ")}
-
- )}
-
-
-
- {candidateTest?.success && (
-
-
-
- {t("cameraWizard.step2.connected")}
-
-
- )}
-
- {candidateTest && !candidateTest.success && (
-
-
-
- {t("cameraWizard.step2.notConnected")}
-
-
- )}
-
-
-
-
-
setShowFull((s) => !s)}
- title={t("cameraWizard.step2.toggleUriView")}
- >
- {showFull ? candidate.uri : maskUri(candidate.uri)}
-
-
-
-
- {copiedUri === candidate.uri ? (
-
- ) : (
-
+
+
+
+
+
+ {t("cameraWizard.step2.candidateStreamTitle", {
+ number: (index ?? 0) + 1,
+ })}
+
+ {candidateTest?.success && (
+
+ {[
+ candidateTest.resolution,
+ candidateTest.fps
+ ? `${candidateTest.fps} ${t(
+ "cameraWizard.testResultLabels.fps",
+ )}`
+ : null,
+ candidateTest.videoCodec,
+ candidateTest.audioCodec,
+ ]
+ .filter(Boolean)
+ .join(" · ")}
+
)}
-
+
+
+ {candidateTest?.success && (
+
+
+
+ {t("cameraWizard.step2.connected")}
+
+
+ )}
+
+ {candidateTest && !candidateTest.success && (
+
+
+
+ {t("cameraWizard.step2.notConnected")}
+
+
+ )}
+
+
+
+
+
setShowFull((s) => !s)}
+ title={t("cameraWizard.step2.toggleUriView")}
+ >
+ {showFull ? candidate.uri : maskUri(candidate.uri)}
+
+
+
+
+ {copiedUri === candidate.uri ? (
+
+ ) : (
+
+ )}
+
+
+
testCandidate?.(candidate.uri)}
+ className="h-8 px-3 text-sm"
+ >
+ {isTesting ? (
+ <>
+ {" "}
+ {t("cameraWizard.step2.testConnection")}
+ >
+ ) : (
+ t("cameraWizard.step2.testConnection")
+ )}
+
+
+
+
+
testCandidate?.(candidate.uri)}
+ onClick={onUse}
+ variant="select"
className="h-8 px-3 text-sm"
>
- {isTesting ? (
-
- ) : (
- t("cameraWizard.step2.testConnection")
- )}
+ {t("cameraWizard.step2.useCandidate")}
-
-
-
- {t("cameraWizard.step2.useCandidate")}
-
-
-
-
+
+
);
}
diff --git a/web/src/components/settings/wizard/ProbeDialog.tsx b/web/src/components/settings/wizard/ProbeDialog.tsx
deleted file mode 100644
index c340426d0..000000000
--- a/web/src/components/settings/wizard/ProbeDialog.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import { useTranslation } from "react-i18next";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogDescription,
-} from "@/components/ui/dialog";
-import { Button } from "@/components/ui/button";
-import OnvifProbeResults from "./OnvifProbeResults";
-import {} from "@/components/ui/card";
-import ActivityIndicator from "@/components/indicators/activity-indicator";
-import type {
- OnvifProbeResponse,
- CandidateTestMap,
-} from "@/types/cameraWizard";
-import StepIndicator from "@/components/indicators/StepIndicator";
-
-type ProbeDialogProps = {
- open: boolean;
- onOpenChange: (open: boolean) => void;
- isLoading: boolean;
- isError: boolean;
- error?: string;
- probeResult?: OnvifProbeResponse | null;
- onSelectCandidate: (uri: string) => void;
- onRetry: () => void;
- selectedCandidateUris?: string[];
- testAllSelectedCandidates?: () => void;
- isTesting?: boolean;
- testStatus?: string;
- testCandidate?: (uri: string) => void;
- candidateTests?: CandidateTestMap;
- testingCandidates?: Record
;
-};
-
-export default function ProbeDialog({
- open,
- onOpenChange,
- isLoading,
- isError,
- error,
- probeResult,
- onSelectCandidate,
- onRetry,
- selectedCandidateUris,
- testAllSelectedCandidates,
- isTesting,
- testStatus,
- testCandidate,
- candidateTests,
- testingCandidates,
-}: ProbeDialogProps) {
- const { t } = useTranslation(["views/settings"]);
-
- const STEPS = [
- "cameraWizard.steps.nameAndConnection",
- "cameraWizard.steps.streamConfiguration",
- "cameraWizard.steps.validationAndTesting",
- ];
-
- return (
-
-
-
-
- {t("cameraWizard.title")}
- {t("cameraWizard.description")}
-
-
-
-
-
-
-
- {isTesting && testStatus && (
-
- )}
-
-
onOpenChange(false)}
- >
- {t("button.back", { ns: "common" })}
-
-
-
0) ||
- isLoading ||
- isTesting
- }
- variant="select"
- >
-
- {isTesting && }
- {t("cameraWizard.step1.testConnection")}
-
-
-
-
-
-
- );
-}
diff --git a/web/src/components/settings/wizard/Step2ProbeOrSnapshot.tsx b/web/src/components/settings/wizard/Step2ProbeOrSnapshot.tsx
index 8e3234f9e..b16ddca28 100644
--- a/web/src/components/settings/wizard/Step2ProbeOrSnapshot.tsx
+++ b/web/src/components/settings/wizard/Step2ProbeOrSnapshot.tsx
@@ -17,7 +17,7 @@ import type {
} from "@/types/cameraWizard";
import { FaCircleCheck } from "react-icons/fa6";
import { Card, CardContent, CardTitle } from "../../ui/card";
-import ProbeDialog from "./ProbeDialog";
+import OnvifProbeResults from "./OnvifProbeResults";
import { CAMERA_BRANDS } from "@/types/cameraWizard";
import { detectReolinkCamera } from "@/utils/cameraUtil";
@@ -45,16 +45,15 @@ export default function Step2ProbeOrSnapshot({
const [probeResult, setProbeResult] = useState(
null,
);
- const [probeDialogOpen, setProbeDialogOpen] = useState(false);
+ const [testingCandidates, setTestingCandidates] = useState<
+ Record
+ >({} as Record);
const [selectedCandidateUris, setSelectedCandidateUris] = useState(
[],
);
const [candidateTests, setCandidateTests] = useState(
{} as CandidateTestMap,
);
- const [testingCandidates, setTestingCandidates] = useState<
- Record
- >({} as Record);
const handleSelectCandidate = useCallback((uri: string) => {
setSelectedCandidateUris((s) => {
@@ -205,7 +204,6 @@ export default function Step2ProbeOrSnapshot({
if (response.data && response.data.success) {
setProbeResult(response.data);
- setProbeDialogOpen(true);
} else {
setProbeError(response.data?.message || "Probe failed");
}
@@ -266,7 +264,6 @@ export default function Step2ProbeOrSnapshot({
if (streamConfigs.length > 0) {
onNext({ streams: streamConfigs });
toast.success(t("cameraWizard.step2.testSuccess"));
- setProbeDialogOpen(false);
} else {
toast.error(
t("cameraWizard.commonErrors.testFailed", {
@@ -459,29 +456,18 @@ export default function Step2ProbeOrSnapshot({
return (
{probeMode ? (
- // Probe mode: show probe dialog
+ // Probe mode: show probe results directly
<>
{probeResult && (
-
-
{
- setProbeDialogOpen(open);
- // If dialog is being closed (open=false), go back
- if (!open) {
- onBack();
- }
- }}
+
+
)}
- {isProbing && !probeResult && (
-
-
- {t("cameraWizard.step2.probing")}
-
- )}
-
- {probeError && !probeResult && (
-
-
{probeError}
-
-
- {t("button.back", { ns: "common" })}
-
-
- {t("cameraWizard.step2.retry")}
-
-
-
- )}
+
>
) : (
// Manual mode: show snapshot and stream details
@@ -566,32 +536,19 @@ export default function Step2ProbeOrSnapshot({
)}
-
-
- {t("button.back", { ns: "common" })}
-
- {testResult?.success ? (
-
- {t("button.continue", { ns: "common" })}
-
- ) : (
-
- {isTesting && }
- {t("cameraWizard.step2.retry")}
-
- )}
-
+
>
)}
@@ -638,3 +595,133 @@ function StreamDetails({ testResult }: { testResult: TestResult }) {
>
);
}
+
+type ProbeFooterProps = {
+ isProbing: boolean;
+ probeError: string | null;
+ onBack: () => void;
+ onTestAll: () => void;
+ onRetry: () => void;
+ isTesting: boolean;
+ selectedCount: number;
+ mode?: "probe" | "manual";
+ manualTestSuccess?: boolean;
+ onContinue?: () => void;
+ onManualTest?: () => void;
+};
+
+function ProbeFooterButtons({
+ isProbing,
+ probeError,
+ onBack,
+ onTestAll,
+ onRetry,
+ isTesting,
+ selectedCount,
+ mode = "probe",
+ manualTestSuccess,
+ onContinue,
+ onManualTest,
+}: ProbeFooterProps) {
+ const { t } = useTranslation(["views/settings"]);
+
+ // Loading footer
+ if (isProbing) {
+ return (
+
+
+
+ {t("cameraWizard.step2.probing")}
+
+
+
+ {t("button.back", { ns: "common" })}
+
+
+
+ {t("cameraWizard.step2.probing")}
+
+
+
+ );
+ }
+
+ // Error footer
+ if (probeError) {
+ return (
+
+
{probeError}
+
+
+ {t("button.back", { ns: "common" })}
+
+
+ {t("cameraWizard.step2.retry")}
+
+
+
+ );
+ }
+
+ // Default footer: show back + test (test disabled if none selected or testing)
+ // If manual mode, show Continue when test succeeded, otherwise show Test (calls onManualTest)
+ if (mode === "manual") {
+ return (
+
+
+ {t("button.back", { ns: "common" })}
+
+ {manualTestSuccess ? (
+
+ {t("button.continue", { ns: "common" })}
+
+ ) : (
+
+ {isTesting && }
+ {t("cameraWizard.step2.retry")}
+
+ )}
+
+ );
+ }
+
+ // Default probe footer
+ return (
+
+
+ {t("button.back", { ns: "common" })}
+
+
+ {isTesting && }
+ {t("cameraWizard.step2.testConnection")}
+
+
+ );
+}