mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-05 22:57:40 +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
|
||||
)
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from typing import Union
|
||||
from typing import Optional, Union
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
@ -23,6 +23,11 @@ class CameraGroupConfig(FrigateBaseModel):
|
||||
title="Sort order",
|
||||
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")
|
||||
@classmethod
|
||||
@ -31,3 +36,11 @@ class CameraGroupConfig(FrigateBaseModel):
|
||||
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), {
|
||||
message: "Invalid icon",
|
||||
}),
|
||||
roles: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const onSubmit = useCallback(
|
||||
@ -756,10 +757,13 @@ export function CameraGroupEdit({
|
||||
const cameraQueries = values.cameras
|
||||
.map((cam) => `&camera_groups.${values.name}.cameras=${cam}`)
|
||||
.join("");
|
||||
const roleQueries = (values.roles ?? [])
|
||||
.map((role) => `&camera_groups.${values.name}.roles=${role}`)
|
||||
.join("");
|
||||
|
||||
axios
|
||||
.put(
|
||||
`config/set?${renamingQuery}${orderQuery}&${iconQuery}${cameraQueries}`,
|
||||
`config/set?${renamingQuery}${orderQuery}&${iconQuery}${cameraQueries}${roleQueries}`,
|
||||
{
|
||||
requires_restart: 0,
|
||||
},
|
||||
@ -828,6 +832,7 @@ export function CameraGroupEdit({
|
||||
name: (editingGroup && editingGroup[0]) ?? "",
|
||||
icon: editingGroup && (editingGroup[1].icon as IconName),
|
||||
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" />
|
||||
|
||||
<div className="flex flex-row gap-2 py-5 md:pb-0">
|
||||
|
||||
@ -345,6 +345,7 @@ export type CameraGroupConfig = {
|
||||
cameras: string[];
|
||||
icon: IconName;
|
||||
order: number;
|
||||
roles?: string[];
|
||||
};
|
||||
|
||||
export type StreamType = "no-streaming" | "smart" | "continuous";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user