mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-23 08:38:22 +03:00
camera group changes for custom viewer roles
- hide camera groups with no accessible cameras - hide camera group edit button
This commit is contained in:
parent
5d3f31175d
commit
bb31dd18a4
@ -87,6 +87,8 @@ type CameraGroupSelectorProps = {
|
|||||||
export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
||||||
const { t } = useTranslation(["components/camera"]);
|
const { t } = useTranslation(["components/camera"]);
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
const allowedCameras = useAllowedCameras();
|
||||||
|
const isCustomRole = useIsCustomRole();
|
||||||
|
|
||||||
// tooltip
|
// tooltip
|
||||||
|
|
||||||
@ -119,10 +121,22 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(config.camera_groups).sort(
|
const allGroups = Object.entries(config.camera_groups);
|
||||||
(a, b) => a[1].order - b[1].order,
|
|
||||||
);
|
// If custom role, filter out groups where user has no accessible cameras
|
||||||
}, [config]);
|
if (isCustomRole) {
|
||||||
|
return allGroups
|
||||||
|
.filter(([, groupConfig]) => {
|
||||||
|
// Check if user has access to at least one camera in this group
|
||||||
|
return groupConfig.cameras.some((cameraName) =>
|
||||||
|
allowedCameras.includes(cameraName),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.sort((a, b) => a[1].order - b[1].order);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allGroups.sort((a, b) => a[1].order - b[1].order);
|
||||||
|
}, [config, allowedCameras, isCustomRole]);
|
||||||
|
|
||||||
// add group
|
// add group
|
||||||
|
|
||||||
@ -139,6 +153,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
|||||||
activeGroup={group}
|
activeGroup={group}
|
||||||
setGroup={setGroup}
|
setGroup={setGroup}
|
||||||
deleteGroup={deleteGroup}
|
deleteGroup={deleteGroup}
|
||||||
|
isCustomRole={isCustomRole}
|
||||||
/>
|
/>
|
||||||
<Scroller className={`${isMobile ? "whitespace-nowrap" : ""}`}>
|
<Scroller className={`${isMobile ? "whitespace-nowrap" : ""}`}>
|
||||||
<div
|
<div
|
||||||
@ -206,14 +221,16 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<Button
|
{!isCustomRole && (
|
||||||
className="bg-secondary text-muted-foreground"
|
<Button
|
||||||
aria-label={t("group.add")}
|
className="bg-secondary text-muted-foreground"
|
||||||
size="xs"
|
aria-label={t("group.add")}
|
||||||
onClick={() => setAddGroup(true)}
|
size="xs"
|
||||||
>
|
onClick={() => setAddGroup(true)}
|
||||||
<LuPlus className="size-4 text-primary" />
|
>
|
||||||
</Button>
|
<LuPlus className="size-4 text-primary" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{isMobile && <ScrollBar orientation="horizontal" className="h-0" />}
|
{isMobile && <ScrollBar orientation="horizontal" className="h-0" />}
|
||||||
</div>
|
</div>
|
||||||
</Scroller>
|
</Scroller>
|
||||||
@ -228,6 +245,7 @@ type NewGroupDialogProps = {
|
|||||||
activeGroup?: string;
|
activeGroup?: string;
|
||||||
setGroup: (value: string | undefined, replace?: boolean | undefined) => void;
|
setGroup: (value: string | undefined, replace?: boolean | undefined) => void;
|
||||||
deleteGroup: () => void;
|
deleteGroup: () => void;
|
||||||
|
isCustomRole?: boolean;
|
||||||
};
|
};
|
||||||
function NewGroupDialog({
|
function NewGroupDialog({
|
||||||
open,
|
open,
|
||||||
@ -236,6 +254,7 @@ function NewGroupDialog({
|
|||||||
activeGroup,
|
activeGroup,
|
||||||
setGroup,
|
setGroup,
|
||||||
deleteGroup,
|
deleteGroup,
|
||||||
|
isCustomRole,
|
||||||
}: NewGroupDialogProps) {
|
}: NewGroupDialogProps) {
|
||||||
const { t } = useTranslation(["components/camera"]);
|
const { t } = useTranslation(["components/camera"]);
|
||||||
const { mutate: updateConfig } = useSWR<FrigateConfig>("config");
|
const { mutate: updateConfig } = useSWR<FrigateConfig>("config");
|
||||||
@ -371,28 +390,30 @@ function NewGroupDialog({
|
|||||||
>
|
>
|
||||||
<Title>{t("group.label")}</Title>
|
<Title>{t("group.label")}</Title>
|
||||||
<Description className="sr-only">{t("group.edit")}</Description>
|
<Description className="sr-only">{t("group.edit")}</Description>
|
||||||
<div
|
{!isCustomRole && (
|
||||||
className={cn(
|
<div
|
||||||
"absolute",
|
|
||||||
isDesktop && "right-6 top-10",
|
|
||||||
isMobile && "absolute right-0 top-4",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className={cn(
|
className={cn(
|
||||||
isDesktop &&
|
"absolute",
|
||||||
"size-6 rounded-md bg-secondary-foreground p-1 text-background",
|
isDesktop && "right-6 top-10",
|
||||||
isMobile && "text-secondary-foreground",
|
isMobile && "absolute right-0 top-4",
|
||||||
)}
|
)}
|
||||||
aria-label={t("group.add")}
|
|
||||||
onClick={() => {
|
|
||||||
setEditState("add");
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<LuPlus />
|
<Button
|
||||||
</Button>
|
size="sm"
|
||||||
</div>
|
className={cn(
|
||||||
|
isDesktop &&
|
||||||
|
"size-6 rounded-md bg-secondary-foreground p-1 text-background",
|
||||||
|
isMobile && "text-secondary-foreground",
|
||||||
|
)}
|
||||||
|
aria-label={t("group.add")}
|
||||||
|
onClick={() => {
|
||||||
|
setEditState("add");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LuPlus />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Header>
|
</Header>
|
||||||
<div className="flex flex-col gap-4 md:gap-3">
|
<div className="flex flex-col gap-4 md:gap-3">
|
||||||
{currentGroups.map((group) => (
|
{currentGroups.map((group) => (
|
||||||
@ -401,6 +422,7 @@ function NewGroupDialog({
|
|||||||
group={group}
|
group={group}
|
||||||
onDeleteGroup={() => onDeleteGroup(group[0])}
|
onDeleteGroup={() => onDeleteGroup(group[0])}
|
||||||
onEditGroup={() => onEditGroup(group)}
|
onEditGroup={() => onEditGroup(group)}
|
||||||
|
isReadOnly={isCustomRole}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -512,12 +534,14 @@ type CameraGroupRowProps = {
|
|||||||
group: [string, CameraGroupConfig];
|
group: [string, CameraGroupConfig];
|
||||||
onDeleteGroup: () => void;
|
onDeleteGroup: () => void;
|
||||||
onEditGroup: () => void;
|
onEditGroup: () => void;
|
||||||
|
isReadOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CameraGroupRow({
|
export function CameraGroupRow({
|
||||||
group,
|
group,
|
||||||
onDeleteGroup,
|
onDeleteGroup,
|
||||||
onEditGroup,
|
onEditGroup,
|
||||||
|
isReadOnly,
|
||||||
}: CameraGroupRowProps) {
|
}: CameraGroupRowProps) {
|
||||||
const { t } = useTranslation(["components/camera"]);
|
const { t } = useTranslation(["components/camera"]);
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
@ -564,7 +588,7 @@ export function CameraGroupRow({
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
{isMobile && (
|
{isMobile && !isReadOnly && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenu modal={!isDesktop}>
|
<DropdownMenu modal={!isDesktop}>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
@ -589,7 +613,7 @@ export function CameraGroupRow({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!isMobile && (
|
{!isMobile && !isReadOnly && (
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user