fix camera group access for non admin users

changes from previous PR wrongly included users from the standard viewer role (but excluded custom viewer roles)
This commit is contained in:
Josh Hawkins 2025-11-26 10:42:22 -06:00
parent 5919b56ffb
commit 890ae6ae50
3 changed files with 19 additions and 19 deletions

View File

@ -78,7 +78,7 @@ import { useStreamingSettings } from "@/context/streaming-settings-provider";
import { Trans, useTranslation } from "react-i18next";
import { CameraNameLabel } from "../camera/FriendlyNameLabel";
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
import { useIsCustomRole } from "@/hooks/use-is-custom-role";
import { useIsAdmin } from "@/hooks/use-is-admin";
type CameraGroupSelectorProps = {
className?: string;
@ -88,7 +88,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
const { t } = useTranslation(["components/camera"]);
const { data: config } = useSWR<FrigateConfig>("config");
const allowedCameras = useAllowedCameras();
const isCustomRole = useIsCustomRole();
const isAdmin = useIsAdmin();
// tooltip
@ -124,7 +124,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
const allGroups = Object.entries(config.camera_groups);
// If custom role, filter out groups where user has no accessible cameras
if (isCustomRole) {
if (!isAdmin) {
return allGroups
.filter(([, groupConfig]) => {
// Check if user has access to at least one camera in this group
@ -136,7 +136,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
}
return allGroups.sort((a, b) => a[1].order - b[1].order);
}, [config, allowedCameras, isCustomRole]);
}, [config, allowedCameras, isAdmin]);
// add group
@ -153,7 +153,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
activeGroup={group}
setGroup={setGroup}
deleteGroup={deleteGroup}
isCustomRole={isCustomRole}
isAdmin={isAdmin}
/>
<Scroller className={`${isMobile ? "whitespace-nowrap" : ""}`}>
<div
@ -221,7 +221,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
);
})}
{!isCustomRole && (
{isAdmin && (
<Button
className="bg-secondary text-muted-foreground"
aria-label={t("group.add")}
@ -245,7 +245,7 @@ type NewGroupDialogProps = {
activeGroup?: string;
setGroup: (value: string | undefined, replace?: boolean | undefined) => void;
deleteGroup: () => void;
isCustomRole?: boolean;
isAdmin?: boolean;
};
function NewGroupDialog({
open,
@ -254,7 +254,7 @@ function NewGroupDialog({
activeGroup,
setGroup,
deleteGroup,
isCustomRole,
isAdmin,
}: NewGroupDialogProps) {
const { t } = useTranslation(["components/camera"]);
const { mutate: updateConfig } = useSWR<FrigateConfig>("config");
@ -390,7 +390,7 @@ function NewGroupDialog({
>
<Title>{t("group.label")}</Title>
<Description className="sr-only">{t("group.edit")}</Description>
{!isCustomRole && (
{isAdmin && (
<div
className={cn(
"absolute",
@ -422,7 +422,7 @@ function NewGroupDialog({
group={group}
onDeleteGroup={() => onDeleteGroup(group[0])}
onEditGroup={() => onEditGroup(group)}
isReadOnly={isCustomRole}
isReadOnly={!isAdmin}
/>
))}
</div>
@ -677,7 +677,7 @@ export function CameraGroupEdit({
);
const allowedCameras = useAllowedCameras();
const isCustomRole = useIsCustomRole();
const isAdmin = useIsAdmin();
const [openCamera, setOpenCamera] = useState<string | null>();
@ -867,7 +867,7 @@ export function CameraGroupEdit({
<FormMessage />
{[
...(birdseyeConfig?.enabled &&
(!isCustomRole || "birdseye" in allowedCameras)
(isAdmin || "birdseye" in allowedCameras)
? ["birdseye"]
: []),
...Object.keys(config?.cameras ?? {})

View File

@ -14,12 +14,12 @@ import { useTranslation } from "react-i18next";
import { useEffect, useMemo, useRef } from "react";
import useSWR from "swr";
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
import { useIsCustomRole } from "@/hooks/use-is-custom-role";
import { useIsAdmin } from "@/hooks/use-is-admin";
function Live() {
const { t } = useTranslation(["views/live"]);
const { data: config } = useSWR<FrigateConfig>("config");
const isCustomRole = useIsCustomRole();
const isAdmin = useIsAdmin();
// selection
@ -94,7 +94,7 @@ function Live() {
const includesBirdseye = useMemo(() => {
// Restricted users should never have access to birdseye
if (isCustomRole) {
if (!isAdmin) {
return false;
}
@ -109,7 +109,7 @@ function Live() {
} else {
return false;
}
}, [config, cameraGroup, isCustomRole]);
}, [config, cameraGroup, isAdmin]);
const cameras = useMemo(() => {
if (!config) {

View File

@ -54,7 +54,7 @@ import { useTranslation } from "react-i18next";
import { EmptyCard } from "@/components/card/EmptyCard";
import { BsFillCameraVideoOffFill } from "react-icons/bs";
import { AuthContext } from "@/context/auth-context";
import { useIsCustomRole } from "@/hooks/use-is-custom-role";
import { useIsAdmin } from "@/hooks/use-is-admin";
type LiveDashboardViewProps = {
cameras: CameraConfig[];
@ -661,10 +661,10 @@ export default function LiveDashboardView({
function NoCameraView() {
const { t } = useTranslation(["views/live"]);
const { auth } = useContext(AuthContext);
const isCustomRole = useIsCustomRole();
const isAdmin = useIsAdmin();
// Check if this is a restricted user with no cameras in this group
const isRestricted = isCustomRole && auth.isAuthenticated;
const isRestricted = !isAdmin && auth.isAuthenticated;
return (
<div className="flex size-full items-center justify-center">