permit birdseye access if user has viewer role or a custom viewer role that has access to all cameras

This commit is contained in:
Josh Hawkins 2026-02-27 17:28:53 -06:00
parent 0310a9654d
commit 22efd2c46b
3 changed files with 37 additions and 9 deletions

View File

@ -77,6 +77,7 @@ import { useStreamingSettings } from "@/context/streaming-settings-provider";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { CameraNameLabel } from "../camera/FriendlyNameLabel"; import { CameraNameLabel } from "../camera/FriendlyNameLabel";
import { useAllowedCameras } from "@/hooks/use-allowed-cameras"; import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
import { useHasFullCameraAccess } from "@/hooks/use-has-full-camera-access";
import { useIsAdmin } from "@/hooks/use-is-admin"; import { useIsAdmin } from "@/hooks/use-is-admin";
import { useUserPersistedOverlayState } from "@/hooks/use-overlay-state"; import { useUserPersistedOverlayState } from "@/hooks/use-overlay-state";
@ -677,7 +678,7 @@ export function CameraGroupEdit({
); );
const allowedCameras = useAllowedCameras(); const allowedCameras = useAllowedCameras();
const isAdmin = useIsAdmin(); const hasFullCameraAccess = useHasFullCameraAccess();
const [openCamera, setOpenCamera] = useState<string | null>(); const [openCamera, setOpenCamera] = useState<string | null>();
@ -866,8 +867,7 @@ export function CameraGroupEdit({
<FormDescription>{t("group.cameras.desc")}</FormDescription> <FormDescription>{t("group.cameras.desc")}</FormDescription>
<FormMessage /> <FormMessage />
{[ {[
...(birdseyeConfig?.enabled && ...(birdseyeConfig?.enabled && hasFullCameraAccess
(isAdmin || "birdseye" in allowedCameras)
? ["birdseye"] ? ["birdseye"]
: []), : []),
...Object.keys(config?.cameras ?? {}) ...Object.keys(config?.cameras ?? {})

View File

@ -0,0 +1,26 @@
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
/**
* Returns true if the current user has access to all cameras.
* This is used to determine birdseye access users who can see
* all cameras should also be able to see the birdseye view.
*/
export function useHasFullCameraAccess() {
const allowedCameras = useAllowedCameras();
const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false,
});
if (!config?.cameras) return false;
const enabledCameraNames = Object.entries(config.cameras)
.filter(([, cam]) => cam.enabled_in_config)
.map(([name]) => name);
return (
enabledCameraNames.length > 0 &&
enabledCameraNames.every((name) => allowedCameras.includes(name))
);
}

View File

@ -11,12 +11,12 @@ import { useTranslation } from "react-i18next";
import { useEffect, useMemo, useRef } from "react"; import { useEffect, useMemo, useRef } from "react";
import useSWR from "swr"; import useSWR from "swr";
import { useAllowedCameras } from "@/hooks/use-allowed-cameras"; import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
import { useIsAdmin } from "@/hooks/use-is-admin"; import { useHasFullCameraAccess } from "@/hooks/use-has-full-camera-access";
function Live() { function Live() {
const { t } = useTranslation(["views/live"]); const { t } = useTranslation(["views/live"]);
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const isAdmin = useIsAdmin(); const hasFullCameraAccess = useHasFullCameraAccess();
// selection // selection
@ -90,8 +90,8 @@ function Live() {
const allowedCameras = useAllowedCameras(); const allowedCameras = useAllowedCameras();
const includesBirdseye = useMemo(() => { const includesBirdseye = useMemo(() => {
// Restricted users should never have access to birdseye // Users without access to all cameras should not have access to birdseye
if (!isAdmin) { if (!hasFullCameraAccess) {
return false; return false;
} }
@ -106,7 +106,7 @@ function Live() {
} else { } else {
return false; return false;
} }
}, [config, cameraGroup, isAdmin]); }, [config, cameraGroup, hasFullCameraAccess]);
const cameras = useMemo(() => { const cameras = useMemo(() => {
if (!config) { if (!config) {
@ -151,7 +151,9 @@ function Live() {
return ( return (
<div className="size-full" ref={mainRef}> <div className="size-full" ref={mainRef}>
{selectedCameraName === "birdseye" ? ( {selectedCameraName === "birdseye" &&
hasFullCameraAccess &&
config?.birdseye?.enabled ? (
<LiveBirdseyeView <LiveBirdseyeView
supportsFullscreen={supportsFullScreen} supportsFullscreen={supportsFullScreen}
fullscreen={fullscreen} fullscreen={fullscreen}