From bb21dfe1cc75b71b35ed53869530bb6d3822e4e6 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 20 May 2026 20:55:06 -0500 Subject: [PATCH] filter replay camera from camera selectors --- .../classification/wizard/Step2StateArea.tsx | 2 ++ .../NotificationsSettingsExtras.tsx | 5 +++- .../components/overlay/CreateRoleDialog.tsx | 5 +++- .../overlay/EditRoleCamerasDialog.tsx | 5 +++- web/src/components/overlay/ExportDialog.tsx | 5 +++- .../overlay/detail/ObjectPathPlotter.tsx | 23 +++++++++++-------- web/src/hooks/use-config-override.ts | 15 ++++++++---- web/src/hooks/use-has-full-camera-access.ts | 3 ++- web/src/pages/Events.tsx | 2 +- web/src/pages/Settings.tsx | 8 ++++++- web/src/views/events/EventView.tsx | 9 +++++--- .../views/settings/CameraManagementView.tsx | 16 ++++++++++--- .../settings/FrigatePlusSettingsView.tsx | 9 ++++---- web/src/views/settings/ProfilesView.tsx | 5 +++- web/src/views/system/CameraMetrics.tsx | 3 ++- 15 files changed, 83 insertions(+), 32 deletions(-) diff --git a/web/src/components/classification/wizard/Step2StateArea.tsx b/web/src/components/classification/wizard/Step2StateArea.tsx index efba0d358a..84367a877d 100644 --- a/web/src/components/classification/wizard/Step2StateArea.tsx +++ b/web/src/components/classification/wizard/Step2StateArea.tsx @@ -14,6 +14,7 @@ import Konva from "konva"; import { useResizeObserver } from "@/hooks/resize-observer"; import { useApiHost } from "@/api"; import { resolveCameraName } from "@/hooks/use-camera-friendly-name"; +import { isReplayCamera } from "@/utils/cameraUtil"; import Heading from "@/components/ui/heading"; import { isMobile } from "react-device-detect"; import { cn } from "@/lib/utils"; @@ -67,6 +68,7 @@ export default function Step2StateArea({ ([name, cam]) => cam.enabled && cam.enabled_in_config && + !isReplayCamera(name) && !selectedCameraNames.includes(name), ) .map(([name]) => ({ diff --git a/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx b/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx index b97c90448d..fb53055bcc 100644 --- a/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx +++ b/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx @@ -57,6 +57,7 @@ import isEqual from "lodash/isEqual"; import set from "lodash/set"; import type { ConfigSectionData, JsonObject } from "@/types/configForm"; import { sanitizeSectionData } from "@/utils/configUtil"; +import { isReplayCamera } from "@/utils/cameraUtil"; import type { SectionRendererProps } from "./registry"; const NOTIFICATION_SERVICE_WORKER = "/notifications-worker.js"; @@ -94,7 +95,7 @@ export default function NotificationsSettingsExtras({ return Object.values(config.cameras) .sort((aConf, bConf) => aConf.ui.order - bConf.ui.order) - .filter((c) => c.enabled_in_config); + .filter((c) => c.enabled_in_config && !isReplayCamera(c.name)); }, [config]); const notificationCameras = useMemo(() => { @@ -106,6 +107,7 @@ export default function NotificationsSettingsExtras({ .filter( (conf) => conf.enabled_in_config && + !isReplayCamera(conf.name) && conf.notifications && conf.notifications.enabled_in_config, ) @@ -359,6 +361,7 @@ export default function NotificationsSettingsExtras({ Object.values(config.cameras).some( (c) => c.enabled_in_config && + !isReplayCamera(c.name) && c.notifications && c.notifications.enabled_in_config, ), diff --git a/web/src/components/overlay/CreateRoleDialog.tsx b/web/src/components/overlay/CreateRoleDialog.tsx index 023d2b6650..2a1b79b761 100644 --- a/web/src/components/overlay/CreateRoleDialog.tsx +++ b/web/src/components/overlay/CreateRoleDialog.tsx @@ -26,6 +26,7 @@ import { import { useTranslation } from "react-i18next"; import { FrigateConfig } from "@/types/frigateConfig"; import { CameraNameLabel } from "../camera/FriendlyNameLabel"; +import { isReplayCamera } from "@/utils/cameraUtil"; import { isDesktop, isMobile } from "react-device-detect"; import { cn } from "@/lib/utils"; import { @@ -52,7 +53,9 @@ export default function CreateRoleDialog({ const { t } = useTranslation(["views/settings"]); const [isLoading, setIsLoading] = useState(false); - const cameras = Object.keys(config.cameras || {}); + const cameras = Object.keys(config.cameras || {}).filter( + (name) => !isReplayCamera(name), + ); const existingRoles = Object.keys(config.auth?.roles || {}); diff --git a/web/src/components/overlay/EditRoleCamerasDialog.tsx b/web/src/components/overlay/EditRoleCamerasDialog.tsx index f533fc5b85..f23dc0931a 100644 --- a/web/src/components/overlay/EditRoleCamerasDialog.tsx +++ b/web/src/components/overlay/EditRoleCamerasDialog.tsx @@ -25,6 +25,7 @@ import { import { Trans, useTranslation } from "react-i18next"; import { FrigateConfig } from "@/types/frigateConfig"; import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel"; +import { isReplayCamera } from "@/utils/cameraUtil"; type EditRoleCamerasOverlayProps = { show: boolean; @@ -46,7 +47,9 @@ export default function EditRoleCamerasDialog({ const { t } = useTranslation(["views/settings"]); const [isLoading, setIsLoading] = useState(false); - const cameras = Object.keys(config.cameras || {}); + const cameras = Object.keys(config.cameras || {}).filter( + (name) => !isReplayCamera(name), + ); const formSchema = z.object({ cameras: z diff --git a/web/src/components/overlay/ExportDialog.tsx b/web/src/components/overlay/ExportDialog.tsx index 0d57821fc1..6d407ecc34 100644 --- a/web/src/components/overlay/ExportDialog.tsx +++ b/web/src/components/overlay/ExportDialog.tsx @@ -54,6 +54,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; import { Textarea } from "../ui/textarea"; import { useNavigate } from "react-router-dom"; import { useIsAdmin } from "@/hooks/use-is-admin"; +import { isReplayCamera } from "@/utils/cameraUtil"; const EXPORT_OPTIONS = [ "1", @@ -448,7 +449,9 @@ export function ExportContent({ ); const cameraActivities = useMemo(() => { - const allCameraIds = Object.keys(config?.cameras ?? {}); + const allCameraIds = Object.keys(config?.cameras ?? {}).filter( + (name) => !isReplayCamera(name), + ); const byCamera = new Map(); events?.forEach((event) => { diff --git a/web/src/components/overlay/detail/ObjectPathPlotter.tsx b/web/src/components/overlay/detail/ObjectPathPlotter.tsx index 1fcf02d1db..aa65ae4091 100644 --- a/web/src/components/overlay/detail/ObjectPathPlotter.tsx +++ b/web/src/components/overlay/detail/ObjectPathPlotter.tsx @@ -13,6 +13,7 @@ import { } from "@/components/ui/select"; import { Card, CardContent } from "@/components/ui/card"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; +import { isReplayCamera } from "@/utils/cameraUtil"; import { useTimezone } from "@/hooks/use-date-utils"; import { Button } from "@/components/ui/button"; import { LuX } from "react-icons/lu"; @@ -36,11 +37,16 @@ export default function ObjectPathPlotter() { const [currentPage, setCurrentPage] = useState(1); const eventsPerPage = 20; + const cameraNames = useMemo(() => { + if (!config) return []; + return Object.keys(config.cameras).filter((name) => !isReplayCamera(name)); + }, [config]); + useEffect(() => { - if (config && !selectedCamera) { - setSelectedCamera(Object.keys(config.cameras)[0]); + if (cameraNames.length > 0 && !selectedCamera) { + setSelectedCamera(cameraNames[0]); } - }, [config, selectedCamera]); + }, [cameraNames, selectedCamera]); const searchQuery = useMemo(() => { if (!selectedCamera) return null; @@ -143,12 +149,11 @@ export default function ObjectPathPlotter() { - {config && - Object.keys(config.cameras).map((cameraName) => ( - - {cameraName} - - ))} + {cameraNames.map((cameraName) => ( + + {cameraName} + + ))}