From 555ef89800dd36bf6e7f46778a2a3b6523a36ff1 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 21 May 2026 09:12:53 -0500 Subject: [PATCH] Debug replay fixes (#23276) * filter replay camera from camera selectors * add face rec and lpr to replay configuration sheet * add missing config topic subscriptions in embeddings maintainer * pop replay camera from config object when stopping --- frigate/debug_replay.py | 1 + frigate/embeddings/maintainer.py | 7 +++++ .../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/Replay.tsx | 28 +++++++++++++++++++ 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 +- 18 files changed, 119 insertions(+), 32 deletions(-) diff --git a/frigate/debug_replay.py b/frigate/debug_replay.py index 557e6447fd..b137e24b93 100644 --- a/frigate/debug_replay.py +++ b/frigate/debug_replay.py @@ -169,6 +169,7 @@ class DebugReplayManager: CameraConfigUpdateTopic(CameraConfigUpdateEnum.remove, replay_name), frigate_config.cameras[replay_name], ) + frigate_config.cameras.pop(replay_name, None) if replay_name is not None: self._cleanup_db(replay_name) diff --git a/frigate/embeddings/maintainer.py b/frigate/embeddings/maintainer.py index 48c5cdd79b..52bdf5d915 100644 --- a/frigate/embeddings/maintainer.py +++ b/frigate/embeddings/maintainer.py @@ -98,10 +98,17 @@ class EmbeddingMaintainer(threading.Thread): [ CameraConfigUpdateEnum.add, CameraConfigUpdateEnum.remove, + CameraConfigUpdateEnum.detect, + CameraConfigUpdateEnum.face_recognition, + CameraConfigUpdateEnum.ffmpeg, + CameraConfigUpdateEnum.lpr, + CameraConfigUpdateEnum.motion, + CameraConfigUpdateEnum.objects, CameraConfigUpdateEnum.object_genai, CameraConfigUpdateEnum.review, CameraConfigUpdateEnum.review_genai, CameraConfigUpdateEnum.semantic_search, + CameraConfigUpdateEnum.zones, ], ) self.enrichment_config_subscriber = ConfigSubscriber("config/") 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} + + ))}