diff --git a/frigate/api/media.py b/frigate/api/media.py index 3cfd97674..2ddabc631 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -24,6 +24,7 @@ from tzlocal import get_localzone_name from frigate.api.auth import ( allow_any_authenticated, require_camera_access, + require_role, ) from frigate.api.defs.query.media_query_parameters import ( Extension, @@ -1005,6 +1006,23 @@ def grid_snapshot( ) +@router.delete( + "/{camera_name}/region_grid", dependencies=[Depends(require_role("admin"))] +) +def clear_region_grid(request: Request, camera_name: str): + """Clear the region grid for a camera.""" + if camera_name not in request.app.frigate_config.cameras: + return JSONResponse( + content={"success": False, "message": "Camera not found"}, + status_code=404, + ) + + Regions.delete().where(Regions.camera == camera_name).execute() + return JSONResponse( + content={"success": True, "message": "Region grid cleared"}, + ) + + @router.get( "/events/{event_id}/snapshot-clean.webp", dependencies=[Depends(require_camera_access)], diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 0b61b278b..81c9b8075 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -83,7 +83,8 @@ "triggers": "Triggers", "debug": "Debug", "frigateplus": "Frigate+", - "maintenance": "Maintenance" + "mediaSync": "Media sync", + "regionGrid": "Region grid" }, "dialog": { "unsavedChanges": { @@ -1232,6 +1233,16 @@ "previews": "Previews", "exports": "Exports", "recordings": "Recordings" + }, + "regionGrid": { + "title": "Region Grid", + "desc": "The region grid is an optimization that learns where objects of different sizes typically appear in each camera's field of view. Frigate uses this data to efficiently size detection regions. The grid is automatically built over time from tracked object data.", + "clear": "Clear region grid", + "clearConfirmTitle": "Clear Region Grid", + "clearConfirmDesc": "Clearing the region grid is not recommended unless you have recently changed your detector model size or have changed your camera's physical position and are having object tracking issues. The grid will be automatically rebuilt over time as objects are tracked. A Frigate restart is required for changes to take effect.", + "clearSuccess": "Region grid cleared successfully", + "clearError": "Failed to clear region grid", + "restartRequired": "Restart required for region grid changes to take effect" } }, "configForm": { diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index e686ea241..ea6c3d650 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -40,7 +40,8 @@ import UsersView from "@/views/settings/UsersView"; import RolesView from "@/views/settings/RolesView"; import UiSettingsView from "@/views/settings/UiSettingsView"; import FrigatePlusSettingsView from "@/views/settings/FrigatePlusSettingsView"; -import MaintenanceSettingsView from "@/views/settings/MaintenanceSettingsView"; +import MediaSyncSettingsView from "@/views/settings/MediaSyncSettingsView"; +import RegionGridSettingsView from "@/views/settings/RegionGridSettingsView"; import SystemDetectionModelSettingsView from "@/views/settings/SystemDetectionModelSettingsView"; import { SingleSectionPage, @@ -154,7 +155,8 @@ const allSettingsViews = [ "roles", "notifications", "frigateplus", - "maintenance", + "mediaSync", + "regionGrid", ] as const; type SettingsType = (typeof allSettingsViews)[number]; @@ -444,7 +446,10 @@ const settingsGroups = [ }, { label: "maintenance", - items: [{ key: "maintenance", component: MaintenanceSettingsView }], + items: [ + { key: "mediaSync", component: MediaSyncSettingsView }, + { key: "regionGrid", component: RegionGridSettingsView }, + ], }, ]; @@ -471,6 +476,7 @@ const CAMERA_SELECT_BUTTON_PAGES = [ "masksAndZones", "motionTuner", "triggers", + "regionGrid", ]; const ALLOWED_VIEWS_FOR_VIEWER = ["ui", "debug", "notifications"]; @@ -478,7 +484,8 @@ const ALLOWED_VIEWS_FOR_VIEWER = ["ui", "debug", "notifications"]; const LARGE_BOTTOM_MARGIN_PAGES = [ "masksAndZones", "motionTuner", - "maintenance", + "mediaSync", + "regionGrid", ]; // keys for camera sections diff --git a/web/src/views/settings/MaintenanceSettingsView.tsx b/web/src/views/settings/MediaSyncSettingsView.tsx similarity index 99% rename from web/src/views/settings/MaintenanceSettingsView.tsx rename to web/src/views/settings/MediaSyncSettingsView.tsx index df1dd7b90..afbfe3ea1 100644 --- a/web/src/views/settings/MaintenanceSettingsView.tsx +++ b/web/src/views/settings/MediaSyncSettingsView.tsx @@ -15,7 +15,7 @@ import { cn } from "@/lib/utils"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { MediaSyncStats } from "@/types/ws"; -export default function MaintenanceSettingsView() { +export default function MediaSyncSettingsView() { const { t } = useTranslation("views/settings"); const [selectedMediaTypes, setSelectedMediaTypes] = useState([ "all", @@ -103,7 +103,7 @@ export default function MaintenanceSettingsView() {
- + {t("maintenance.sync.title")} diff --git a/web/src/views/settings/RegionGridSettingsView.tsx b/web/src/views/settings/RegionGridSettingsView.tsx new file mode 100644 index 000000000..8ab571a3f --- /dev/null +++ b/web/src/views/settings/RegionGridSettingsView.tsx @@ -0,0 +1,124 @@ +import Heading from "@/components/ui/heading"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Toaster } from "@/components/ui/sonner"; +import { useCallback, useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import axios from "axios"; +import { toast } from "sonner"; +import { StatusBarMessagesContext } from "@/context/statusbar-provider"; +import { cn } from "@/lib/utils"; + +type RegionGridSettingsViewProps = { + selectedCamera: string; +}; + +export default function RegionGridSettingsView({ + selectedCamera, +}: RegionGridSettingsViewProps) { + const { t } = useTranslation("views/settings"); + const { addMessage } = useContext(StatusBarMessagesContext)!; + const [isConfirmOpen, setIsConfirmOpen] = useState(false); + const [isClearing, setIsClearing] = useState(false); + const [imageKey, setImageKey] = useState(0); + + const handleClear = useCallback(async () => { + setIsClearing(true); + + try { + await axios.delete(`${selectedCamera}/region_grid`); + toast.success(t("maintenance.regionGrid.clearSuccess"), { + position: "top-center", + }); + setImageKey((prev) => prev + 1); + addMessage( + "region_grid_restart", + t("maintenance.regionGrid.restartRequired"), + undefined, + "region_grid_settings", + ); + } catch { + toast.error(t("maintenance.regionGrid.clearError"), { + position: "top-center", + }); + } finally { + setIsClearing(false); + setIsConfirmOpen(false); + } + }, [selectedCamera, t, addMessage]); + + return ( + <> +
+ +
+ + {t("maintenance.regionGrid.title")} + + +
+
+

{t("maintenance.regionGrid.desc")}

+
+
+ +
+ {t("maintenance.regionGrid.title")} +
+ +
+ +
+
+
+ + + + + + {t("maintenance.regionGrid.clearConfirmTitle")} + + + {t("maintenance.regionGrid.clearConfirmDesc")} + + + + + {t("button.cancel", { ns: "common" })} + + + {t("maintenance.regionGrid.clear")} + + + + + + ); +}