mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-09 04:35:25 +03:00
Support adding of camera groups and dynamically updating the config
This commit is contained in:
parent
2e9c4a8d63
commit
b064587a68
@ -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(
|
||||
{
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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) {
|
||||
<div
|
||||
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"}>
|
||||
<TooltipTrigger asChild>
|
||||
@ -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<FrigateConfig>("config");
|
||||
function NewGroupDialog({ open, setOpen, index }: NewGroupDialogProps) {
|
||||
const { data: config, mutate: updateConfig } =
|
||||
useSWR<FrigateConfig>("config");
|
||||
const [newTitle, setNewTitle] = useState("");
|
||||
const [icon, setIcon] = useState("");
|
||||
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 (
|
||||
<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>
|
||||
<Input
|
||||
type="text"
|
||||
@ -198,7 +244,10 @@ function NewGroupDialog({ open, setOpen }: NewGroupDialogProps) {
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button variant="select">Submit</Button>
|
||||
{error && <div className="text-danger">{error}</div>}
|
||||
<Button variant="select" onClick={onCreateGroup}>
|
||||
Submit
|
||||
</Button>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user