mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 10:33:11 +03:00
Add ability to clear region grids from the frontend (#22277)
* backend * frontend * i18n * tweaks
This commit is contained in:
parent
02678f4a09
commit
229436c94a
@ -24,6 +24,7 @@ from tzlocal import get_localzone_name
|
|||||||
from frigate.api.auth import (
|
from frigate.api.auth import (
|
||||||
allow_any_authenticated,
|
allow_any_authenticated,
|
||||||
require_camera_access,
|
require_camera_access,
|
||||||
|
require_role,
|
||||||
)
|
)
|
||||||
from frigate.api.defs.query.media_query_parameters import (
|
from frigate.api.defs.query.media_query_parameters import (
|
||||||
Extension,
|
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(
|
@router.get(
|
||||||
"/events/{event_id}/snapshot-clean.webp",
|
"/events/{event_id}/snapshot-clean.webp",
|
||||||
dependencies=[Depends(require_camera_access)],
|
dependencies=[Depends(require_camera_access)],
|
||||||
|
|||||||
@ -83,7 +83,8 @@
|
|||||||
"triggers": "Triggers",
|
"triggers": "Triggers",
|
||||||
"debug": "Debug",
|
"debug": "Debug",
|
||||||
"frigateplus": "Frigate+",
|
"frigateplus": "Frigate+",
|
||||||
"maintenance": "Maintenance"
|
"mediaSync": "Media sync",
|
||||||
|
"regionGrid": "Region grid"
|
||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"unsavedChanges": {
|
"unsavedChanges": {
|
||||||
@ -1232,6 +1233,16 @@
|
|||||||
"previews": "Previews",
|
"previews": "Previews",
|
||||||
"exports": "Exports",
|
"exports": "Exports",
|
||||||
"recordings": "Recordings"
|
"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": {
|
"configForm": {
|
||||||
|
|||||||
@ -40,7 +40,8 @@ import UsersView from "@/views/settings/UsersView";
|
|||||||
import RolesView from "@/views/settings/RolesView";
|
import RolesView from "@/views/settings/RolesView";
|
||||||
import UiSettingsView from "@/views/settings/UiSettingsView";
|
import UiSettingsView from "@/views/settings/UiSettingsView";
|
||||||
import FrigatePlusSettingsView from "@/views/settings/FrigatePlusSettingsView";
|
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 SystemDetectionModelSettingsView from "@/views/settings/SystemDetectionModelSettingsView";
|
||||||
import {
|
import {
|
||||||
SingleSectionPage,
|
SingleSectionPage,
|
||||||
@ -154,7 +155,8 @@ const allSettingsViews = [
|
|||||||
"roles",
|
"roles",
|
||||||
"notifications",
|
"notifications",
|
||||||
"frigateplus",
|
"frigateplus",
|
||||||
"maintenance",
|
"mediaSync",
|
||||||
|
"regionGrid",
|
||||||
] as const;
|
] as const;
|
||||||
type SettingsType = (typeof allSettingsViews)[number];
|
type SettingsType = (typeof allSettingsViews)[number];
|
||||||
|
|
||||||
@ -444,7 +446,10 @@ const settingsGroups = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "maintenance",
|
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",
|
"masksAndZones",
|
||||||
"motionTuner",
|
"motionTuner",
|
||||||
"triggers",
|
"triggers",
|
||||||
|
"regionGrid",
|
||||||
];
|
];
|
||||||
|
|
||||||
const ALLOWED_VIEWS_FOR_VIEWER = ["ui", "debug", "notifications"];
|
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 = [
|
const LARGE_BOTTOM_MARGIN_PAGES = [
|
||||||
"masksAndZones",
|
"masksAndZones",
|
||||||
"motionTuner",
|
"motionTuner",
|
||||||
"maintenance",
|
"mediaSync",
|
||||||
|
"regionGrid",
|
||||||
];
|
];
|
||||||
|
|
||||||
// keys for camera sections
|
// keys for camera sections
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||||
import { MediaSyncStats } from "@/types/ws";
|
import { MediaSyncStats } from "@/types/ws";
|
||||||
|
|
||||||
export default function MaintenanceSettingsView() {
|
export default function MediaSyncSettingsView() {
|
||||||
const { t } = useTranslation("views/settings");
|
const { t } = useTranslation("views/settings");
|
||||||
const [selectedMediaTypes, setSelectedMediaTypes] = useState<string[]>([
|
const [selectedMediaTypes, setSelectedMediaTypes] = useState<string[]>([
|
||||||
"all",
|
"all",
|
||||||
@ -103,7 +103,7 @@ export default function MaintenanceSettingsView() {
|
|||||||
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto px-2 md:order-none">
|
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto px-2 md:order-none">
|
||||||
<div className="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
|
<div className="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<Heading as="h4" className="mb-2">
|
<Heading as="h4" className="mb-2 hidden md:block">
|
||||||
{t("maintenance.sync.title")}
|
{t("maintenance.sync.title")}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
124
web/src/views/settings/RegionGridSettingsView.tsx
Normal file
124
web/src/views/settings/RegionGridSettingsView.tsx
Normal file
@ -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 (
|
||||||
|
<>
|
||||||
|
<div className="flex size-full flex-col md:flex-row">
|
||||||
|
<Toaster position="top-center" closeButton={true} />
|
||||||
|
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto px-2 md:order-none">
|
||||||
|
<Heading as="h4" className="mb-2 hidden md:block">
|
||||||
|
{t("maintenance.regionGrid.title")}
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
<div className="max-w-6xl">
|
||||||
|
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-muted-foreground">
|
||||||
|
<p>{t("maintenance.regionGrid.desc")}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4 max-w-5xl rounded-lg border border-secondary">
|
||||||
|
<img
|
||||||
|
key={imageKey}
|
||||||
|
src={`api/${selectedCamera}/grid.jpg?cache=${imageKey}`}
|
||||||
|
alt={t("maintenance.regionGrid.title")}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex w-full flex-row items-center gap-2 py-2 md:w-[50%]">
|
||||||
|
<Button
|
||||||
|
onClick={() => setIsConfirmOpen(true)}
|
||||||
|
disabled={isClearing}
|
||||||
|
variant="destructive"
|
||||||
|
className="flex flex-1 text-white md:max-w-sm"
|
||||||
|
>
|
||||||
|
{t("maintenance.regionGrid.clear")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AlertDialog open={isConfirmOpen} onOpenChange={setIsConfirmOpen}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
{t("maintenance.regionGrid.clearConfirmTitle")}
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{t("maintenance.regionGrid.clearConfirmDesc")}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>
|
||||||
|
{t("button.cancel", { ns: "common" })}
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant: "destructive" }),
|
||||||
|
"text-white",
|
||||||
|
)}
|
||||||
|
onClick={handleClear}
|
||||||
|
>
|
||||||
|
{t("maintenance.regionGrid.clear")}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user