mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-10 13:15: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()}
|
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(
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user