Support adding of camera groups and dynamically updating the config

This commit is contained in:
Nicolas Mowen 2024-03-06 12:35:39 -07:00
parent 2e9c4a8d63
commit b064587a68
3 changed files with 74 additions and 13 deletions

View File

@ -159,9 +159,9 @@ def config():
config["plus"] = {"enabled": current_app.plus_api.is_active()} config["plus"] = {"enabled": current_app.plus_api.is_active()}
for detector, detector_config in config["detectors"].items(): for detector, detector_config in config["detectors"].items():
detector_config["model"]["labelmap"] = ( detector_config["model"][
current_app.frigate_config.model.merged_labelmap "labelmap"
) ] = current_app.frigate_config.model.merged_labelmap
return jsonify(config) return jsonify(config)
@ -292,7 +292,7 @@ def config_set():
f.close() f.close()
# Validate the config schema # Validate the config schema
try: try:
FrigateConfig.parse_raw(new_raw_config) config_obj = FrigateConfig.parse_raw(new_raw_config)
except Exception: except Exception:
with open(config_file, "w") as f: with open(config_file, "w") as f:
f.write(old_raw_config) f.write(old_raw_config)
@ -314,6 +314,13 @@ def config_set():
500, 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( return make_response(
jsonify( jsonify(
{ {

View File

@ -204,17 +204,22 @@ def update_yaml_from_url(file_path, url):
key_path.pop(i - 1) key_path.pop(i - 1)
except ValueError: except ValueError:
pass 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): def update_yaml_file(file_path, key_path, new_value):
yaml = YAML() yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
with open(file_path, "r") as f: with open(file_path, "r") as f:
data = yaml.load(f) data = yaml.load(f)
data = update_yaml(data, key_path, new_value) 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: with open(file_path, "w") as f:
yaml.dump(data, f) yaml.dump(data, f)

View File

@ -19,6 +19,7 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "../ui/dropdown-menu"; } from "../ui/dropdown-menu";
import FilterCheckBox from "./FilterCheckBox"; import FilterCheckBox from "./FilterCheckBox";
import axios from "axios";
type CameraGroupSelectorProps = { type CameraGroupSelectorProps = {
className?: string; className?: string;
@ -68,7 +69,11 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
<div <div
className={`flex items-center justify-start gap-2 ${className ?? ""} ${isDesktop ? "flex-col" : ""}`} className={`flex items-center justify-start gap-2 ${className ?? ""} ${isDesktop ? "flex-col" : ""}`}
> >
<NewGroupDialog open={addGroup} setOpen={setAddGroup} /> <NewGroupDialog
open={addGroup}
setOpen={setAddGroup}
index={groups.length}
/>
<Tooltip open={tooltip == "home"}> <Tooltip open={tooltip == "home"}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
@ -79,7 +84,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
: "text-muted-foreground bg-secondary focus:text-muted-foreground focus:bg-secondary" : "text-muted-foreground bg-secondary focus:text-muted-foreground focus:bg-secondary"
} }
size="xs" size="xs"
onClick={() => navigate(-1)} onClick={() => (group ? navigate(-1) : null)}
onMouseEnter={() => (isDesktop ? showTooltip("home") : null)} onMouseEnter={() => (isDesktop ? showTooltip("home") : null)}
onMouseLeave={() => (isDesktop ? showTooltip(undefined) : null)} onMouseLeave={() => (isDesktop ? showTooltip(undefined) : null)}
> >
@ -130,16 +135,57 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
type NewGroupDialogProps = { type NewGroupDialogProps = {
open: boolean; open: boolean;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
index: number;
}; };
function NewGroupDialog({ open, setOpen }: NewGroupDialogProps) { function NewGroupDialog({ open, setOpen, index }: NewGroupDialogProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config, mutate: updateConfig } =
useSWR<FrigateConfig>("config");
const [newTitle, setNewTitle] = useState(""); const [newTitle, setNewTitle] = useState("");
const [icon, setIcon] = useState(""); const [icon, setIcon] = useState("");
const [cameras, setCameras] = useState<string[]>([]); const [cameras, setCameras] = useState<string[]>([]);
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 ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="min-w-0 w-80"> <DialogContent className="min-w-0 w-96">
<DialogTitle>Create New Camera Group</DialogTitle> <DialogTitle>Create New Camera Group</DialogTitle>
<Input <Input
type="text" type="text"
@ -198,7 +244,10 @@ function NewGroupDialog({ open, setOpen }: NewGroupDialogProps) {
))} ))}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Button variant="select">Submit</Button> {error && <div className="text-danger">{error}</div>}
<Button variant="select" onClick={onCreateGroup}>
Submit
</Button>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );