mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-06 15:17:37 +03:00
Merge pull request #78 from ibs0d/claude/frigate-role-based-groups-2kRHN
feat: add role-based visibility for camera groups
This commit is contained in:
commit
e947e1ede7
@ -226,6 +226,18 @@ def config(request: Request):
|
|||||||
request.app.frigate_config.model.merged_labelmap
|
request.app.frigate_config.model.merged_labelmap
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# filter camera_groups by role
|
||||||
|
username = request.headers.get("remote-user")
|
||||||
|
role = request.headers.get("remote-role")
|
||||||
|
|
||||||
|
if username and role and role != "admin":
|
||||||
|
filtered_groups = {}
|
||||||
|
for group_name, group_config in config.get("camera_groups", {}).items():
|
||||||
|
group_roles = group_config.get("roles")
|
||||||
|
if group_roles and role in group_roles:
|
||||||
|
filtered_groups[group_name] = group_config
|
||||||
|
config["camera_groups"] = filtered_groups
|
||||||
|
|
||||||
return JSONResponse(content=config)
|
return JSONResponse(content=config)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from pydantic import Field, field_validator
|
from pydantic import Field, field_validator
|
||||||
|
|
||||||
@ -23,6 +23,11 @@ class CameraGroupConfig(FrigateBaseModel):
|
|||||||
title="Sort order",
|
title="Sort order",
|
||||||
description="Numeric order used to sort camera groups in the UI; larger numbers appear later.",
|
description="Numeric order used to sort camera groups in the UI; larger numbers appear later.",
|
||||||
)
|
)
|
||||||
|
roles: Optional[list[str]] = Field(
|
||||||
|
default=None,
|
||||||
|
title="Roles",
|
||||||
|
description="List of roles that can see this camera group. If not set, only admin can see it.",
|
||||||
|
)
|
||||||
|
|
||||||
@field_validator("cameras", mode="before")
|
@field_validator("cameras", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -31,3 +36,11 @@ class CameraGroupConfig(FrigateBaseModel):
|
|||||||
return [v]
|
return [v]
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
@field_validator("roles", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def validate_roles(cls, v):
|
||||||
|
if isinstance(v, str) and "," not in v:
|
||||||
|
return [v]
|
||||||
|
|
||||||
|
return v
|
||||||
|
|||||||
@ -721,6 +721,7 @@ export function CameraGroupEdit({
|
|||||||
.refine((value) => Object.keys(LuIcons).includes(value), {
|
.refine((value) => Object.keys(LuIcons).includes(value), {
|
||||||
message: "Invalid icon",
|
message: "Invalid icon",
|
||||||
}),
|
}),
|
||||||
|
roles: z.array(z.string()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
@ -756,10 +757,13 @@ export function CameraGroupEdit({
|
|||||||
const cameraQueries = values.cameras
|
const cameraQueries = values.cameras
|
||||||
.map((cam) => `&camera_groups.${values.name}.cameras=${cam}`)
|
.map((cam) => `&camera_groups.${values.name}.cameras=${cam}`)
|
||||||
.join("");
|
.join("");
|
||||||
|
const roleQueries = (values.roles ?? [])
|
||||||
|
.map((role) => `&camera_groups.${values.name}.roles=${role}`)
|
||||||
|
.join("");
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.put(
|
.put(
|
||||||
`config/set?${renamingQuery}${orderQuery}&${iconQuery}${cameraQueries}`,
|
`config/set?${renamingQuery}${orderQuery}&${iconQuery}${cameraQueries}${roleQueries}`,
|
||||||
{
|
{
|
||||||
requires_restart: 0,
|
requires_restart: 0,
|
||||||
},
|
},
|
||||||
@ -828,6 +832,7 @@ export function CameraGroupEdit({
|
|||||||
name: (editingGroup && editingGroup[0]) ?? "",
|
name: (editingGroup && editingGroup[0]) ?? "",
|
||||||
icon: editingGroup && (editingGroup[1].icon as IconName),
|
icon: editingGroup && (editingGroup[1].icon as IconName),
|
||||||
cameras: editingGroup && editingGroup[1].cameras,
|
cameras: editingGroup && editingGroup[1].cameras,
|
||||||
|
roles: (editingGroup && editingGroup[1].roles) ?? [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -970,6 +975,49 @@ export function CameraGroupEdit({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{isAdmin && config?.auth?.roles && (
|
||||||
|
<>
|
||||||
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="roles"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("group.roles.label", { defaultValue: "Roles" })}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("group.roles.desc", {
|
||||||
|
defaultValue:
|
||||||
|
"Select which roles can see this camera group. If no roles selected, group is only visible to admins.",
|
||||||
|
})}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
{Object.keys(config.auth.roles)
|
||||||
|
.filter((role) => role !== "admin")
|
||||||
|
.map((role) => (
|
||||||
|
<FormControl key={role}>
|
||||||
|
<div className="flex items-center justify-between gap-1">
|
||||||
|
<span className="mx-2 w-full cursor-pointer text-primary smart-capitalize">
|
||||||
|
{role}
|
||||||
|
</span>
|
||||||
|
<Switch
|
||||||
|
id={`role-${role}`}
|
||||||
|
checked={field.value?.includes(role) ?? false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
const updatedRoles = checked
|
||||||
|
? [...(field.value || []), role]
|
||||||
|
: (field.value || []).filter((r) => r !== role);
|
||||||
|
form.setValue("roles", updatedRoles);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
))}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Separator className="my-2 flex bg-secondary" />
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
|
|
||||||
<div className="flex flex-row gap-2 py-5 md:pb-0">
|
<div className="flex flex-row gap-2 py-5 md:pb-0">
|
||||||
|
|||||||
@ -345,6 +345,7 @@ export type CameraGroupConfig = {
|
|||||||
cameras: string[];
|
cameras: string[];
|
||||||
icon: IconName;
|
icon: IconName;
|
||||||
order: number;
|
order: number;
|
||||||
|
roles?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StreamType = "no-streaming" | "smart" | "continuous";
|
export type StreamType = "no-streaming" | "smart" | "continuous";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user