From 6f8a6f5286f97c7b1b0006d9e463023dd56ab25b Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:17:51 -0500 Subject: [PATCH] frontend --- .../overlay/dialog/DeleteCameraDialog.tsx | 215 ++++++++++++++++++ .../views/settings/CameraManagementView.tsx | 41 +++- 2 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 web/src/components/overlay/dialog/DeleteCameraDialog.tsx diff --git a/web/src/components/overlay/dialog/DeleteCameraDialog.tsx b/web/src/components/overlay/dialog/DeleteCameraDialog.tsx new file mode 100644 index 000000000..472e6bfa7 --- /dev/null +++ b/web/src/components/overlay/dialog/DeleteCameraDialog.tsx @@ -0,0 +1,215 @@ +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Trans } from "react-i18next"; +import axios from "axios"; +import { toast } from "sonner"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Label } from "@/components/ui/label"; +import ActivityIndicator from "@/components/indicators/activity-indicator"; +import { Switch } from "@/components/ui/switch"; + +type DeleteCameraDialogProps = { + show: boolean; + cameras: string[]; + onClose: () => void; + onDeleted: () => void; +}; + +export default function DeleteCameraDialog({ + show, + cameras, + onClose, + onDeleted, +}: DeleteCameraDialogProps) { + const { t } = useTranslation(["views/settings", "common"]); + const [phase, setPhase] = useState<"select" | "confirm">("select"); + const [selectedCamera, setSelectedCamera] = useState(""); + const [deleteExports, setDeleteExports] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + + const handleClose = useCallback(() => { + if (isDeleting) return; + setPhase("select"); + setSelectedCamera(""); + setDeleteExports(false); + onClose(); + }, [isDeleting, onClose]); + + const handleDelete = useCallback(() => { + setPhase("confirm"); + }, []); + + const handleBack = useCallback(() => { + setPhase("select"); + }, []); + + const handleConfirmDelete = useCallback(async () => { + if (!selectedCamera || isDeleting) return; + + setIsDeleting(true); + + try { + await axios.delete( + `cameras/${selectedCamera}?delete_exports=${deleteExports}`, + ); + toast.success( + t("cameraManagement.deleteCameraDialog.success", { + cameraName: selectedCamera, + }), + { position: "top-center" }, + ); + setPhase("select"); + setSelectedCamera(""); + setDeleteExports(false); + onDeleted(); + } catch (error) { + const errorMessage = + axios.isAxiosError(error) && + (error.response?.data?.message || error.response?.data?.detail) + ? error.response?.data?.message || error.response?.data?.detail + : t("cameraManagement.deleteCameraDialog.error", { + cameraName: selectedCamera, + }); + + toast.error(errorMessage, { position: "top-center" }); + } finally { + setIsDeleting(false); + } + }, [selectedCamera, deleteExports, isDeleting, onDeleted, t]); + + return ( + + + {phase === "select" ? ( + <> + + + {t("cameraManagement.deleteCameraDialog.title")} + + + {t("cameraManagement.deleteCameraDialog.description")} + + + + +
+
+ + +
+
+
+ + ) : ( + <> + + + {t("cameraManagement.deleteCameraDialog.confirmTitle")} + + + }} + > + cameraManagement.deleteCameraDialog.confirmWarning + + + +
+ + setDeleteExports(checked === true) + } + /> + +
+ +
+
+ + +
+
+
+ + )} +
+
+ ); +} diff --git a/web/src/views/settings/CameraManagementView.tsx b/web/src/views/settings/CameraManagementView.tsx index df250fab9..1c5168953 100644 --- a/web/src/views/settings/CameraManagementView.tsx +++ b/web/src/views/settings/CameraManagementView.tsx @@ -13,7 +13,8 @@ import { FrigateConfig } from "@/types/frigateConfig"; import { useTranslation } from "react-i18next"; import CameraEditForm from "@/components/settings/CameraEditForm"; import CameraWizardDialog from "@/components/settings/CameraWizardDialog"; -import { LuPlus } from "react-icons/lu"; +import DeleteCameraDialog from "@/components/overlay/dialog/DeleteCameraDialog"; +import { LuPlus, LuTrash2 } from "react-icons/lu"; import { IoMdArrowRoundBack } from "react-icons/io"; import { isDesktop } from "react-device-detect"; import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel"; @@ -45,6 +46,7 @@ export default function CameraManagementView({ undefined, ); // Track camera being edited const [showWizard, setShowWizard] = useState(false); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); // State for restart dialog when enabling a disabled camera const [restartDialogOpen, setRestartDialogOpen] = useState(false); @@ -98,14 +100,26 @@ export default function CameraManagementView({
- +
+ + {enabledCameras.length + disabledCameras.length > 0 && ( + + )} +
{enabledCameras.length > 0 && ( setShowWizard(false)} /> + setShowDeleteDialog(false)} + onDeleted={() => { + setShowDeleteDialog(false); + updateConfig(); + }} + /> setRestartDialogOpen(false)}