From b064587a683e60ee26226434a9a57068982d3e3a Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 6 Mar 2024 12:35:39 -0700 Subject: [PATCH] Support adding of camera groups and dynamically updating the config --- frigate/api/app.py | 15 +++-- frigate/util/builtin.py | 11 +++- .../components/filter/CameraGroupSelector.tsx | 61 +++++++++++++++++-- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/frigate/api/app.py b/frigate/api/app.py index f4f513e14..0064758aa 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -159,9 +159,9 @@ def config(): config["plus"] = {"enabled": current_app.plus_api.is_active()} for detector, detector_config in config["detectors"].items(): - detector_config["model"]["labelmap"] = ( - current_app.frigate_config.model.merged_labelmap - ) + detector_config["model"][ + "labelmap" + ] = current_app.frigate_config.model.merged_labelmap return jsonify(config) @@ -292,7 +292,7 @@ def config_set(): f.close() # Validate the config schema try: - FrigateConfig.parse_raw(new_raw_config) + config_obj = FrigateConfig.parse_raw(new_raw_config) except Exception: with open(config_file, "w") as f: f.write(old_raw_config) @@ -314,6 +314,13 @@ def config_set(): 500, ) + json = request.get_json(silent=True) or {} + + if json.get("requires_restart", 1) == 0: + current_app.frigate_config = FrigateConfig.runtime_config( + config_obj, current_app.plus_api + ) + return make_response( jsonify( { diff --git a/frigate/util/builtin.py b/frigate/util/builtin.py index 30388251d..aa009aa04 100644 --- a/frigate/util/builtin.py +++ b/frigate/util/builtin.py @@ -204,17 +204,22 @@ def update_yaml_from_url(file_path, url): key_path.pop(i - 1) except ValueError: pass - new_value = new_value_list[0] - update_yaml_file(file_path, key_path, new_value) + + if len(new_value_list) > 1: + update_yaml_file(file_path, key_path, new_value_list) + else: + update_yaml_file(file_path, key_path, new_value_list[0]) def update_yaml_file(file_path, key_path, new_value): yaml = YAML() + yaml.indent(mapping=2, sequence=4, offset=2) with open(file_path, "r") as f: data = yaml.load(f) data = update_yaml(data, key_path, new_value) - + with open("/config/test.yaml", "w") as f: + yaml.dump(data, f) with open(file_path, "w") as f: yaml.dump(data, f) diff --git a/web/src/components/filter/CameraGroupSelector.tsx b/web/src/components/filter/CameraGroupSelector.tsx index f737598c1..5ed50be7c 100644 --- a/web/src/components/filter/CameraGroupSelector.tsx +++ b/web/src/components/filter/CameraGroupSelector.tsx @@ -19,6 +19,7 @@ import { DropdownMenuTrigger, } from "../ui/dropdown-menu"; import FilterCheckBox from "./FilterCheckBox"; +import axios from "axios"; type CameraGroupSelectorProps = { className?: string; @@ -68,7 +69,11 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
- + @@ -79,7 +84,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) { : "text-muted-foreground bg-secondary focus:text-muted-foreground focus:bg-secondary" } size="xs" - onClick={() => navigate(-1)} + onClick={() => (group ? navigate(-1) : null)} onMouseEnter={() => (isDesktop ? showTooltip("home") : null)} onMouseLeave={() => (isDesktop ? showTooltip(undefined) : null)} > @@ -130,16 +135,57 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) { type NewGroupDialogProps = { open: boolean; setOpen: (open: boolean) => void; + index: number; }; -function NewGroupDialog({ open, setOpen }: NewGroupDialogProps) { - const { data: config } = useSWR("config"); +function NewGroupDialog({ open, setOpen, index }: NewGroupDialogProps) { + const { data: config, mutate: updateConfig } = + useSWR("config"); const [newTitle, setNewTitle] = useState(""); const [icon, setIcon] = useState(""); const [cameras, setCameras] = useState([]); + const [error, setError] = useState(""); + + const onCreateGroup = useCallback(async () => { + if (!newTitle) { + setError("A title must be selected"); + return; + } + + if (!icon) { + setError("An icon must be selected"); + return; + } + + if (!cameras || cameras.length < 2) { + setError("At least 2 cameras must be selected"); + return; + } + + setError(""); + const orderQuery = `camera_groups.${newTitle}.order=${index}`; + const iconQuery = `camera_groups.${newTitle}.icon=${icon}`; + const cameraQueries = cameras + .map((cam) => `&camera_groups.${newTitle}.cameras=${cam}`) + .join(""); + + const req = axios.put( + `config/set?${orderQuery}&${iconQuery}${cameraQueries}`, + { requires_restart: 0 }, + ); + + setOpen(false); + + if ((await req).status == 200) { + setNewTitle(""); + setIcon(""); + setCameras([]); + updateConfig(); + } + }, [index, cameras, newTitle, icon, setOpen, updateConfig]); return ( - + Create New Camera Group - + {error &&
{error}
} +
);