restrict debug replay UI entry points to admin users

This commit is contained in:
Josh Hawkins 2026-05-23 08:19:38 -05:00
parent ae987c0964
commit ee73da655c
6 changed files with 52 additions and 24 deletions

View File

@ -130,9 +130,15 @@ export default function SearchResultActions({
},
);
} else {
toast.error(t("dialog.toast.error", { error: errorMessage }), {
position: "top-center",
});
toast.error(
t("dialog.toast.error", {
ns: "views/replay",
error: errorMessage,
}),
{
position: "top-center",
},
);
}
})
.finally(() => {
@ -206,7 +212,7 @@ export default function SearchResultActions({
<span>{t("itemMenu.addTrigger.label")}</span>
</MenuItem>
)}
{searchResult.has_clip && (
{isAdmin && searchResult.has_clip && (
<MenuItem
className="cursor-pointer"
aria-label={t("itemMenu.debugReplay.aria")}

View File

@ -9,7 +9,7 @@ import { useTranslation } from "react-i18next";
import { FaFilm } from "react-icons/fa6";
type ActionsDropdownProps = {
onDebugReplayClick: () => void;
onDebugReplayClick?: () => void;
onExportClick: () => void;
onShareTimestampClick: () => void;
};
@ -42,9 +42,11 @@ export default function ActionsDropdown({
<DropdownMenuItem onClick={onShareTimestampClick}>
{t("recording.shareTimestamp.label", { ns: "components/dialog" })}
</DropdownMenuItem>
<DropdownMenuItem onClick={onDebugReplayClick}>
{t("title", { ns: "views/replay" })}
</DropdownMenuItem>
{onDebugReplayClick && (
<DropdownMenuItem onClick={onDebugReplayClick}>
{t("title", { ns: "views/replay" })}
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
);

View File

@ -29,6 +29,7 @@ import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { StartExportResponse } from "@/types/export";
import { ShareTimestampContent } from "./ShareTimestampDialog";
import { useIsAdmin } from "@/hooks/use-is-admin";
type DrawerMode =
| "none"
@ -109,6 +110,7 @@ export default function MobileReviewSettingsDrawer({
"views/replay",
"common",
]);
const isAdmin = useIsAdmin();
const navigate = useNavigate();
const [drawerMode, setDrawerMode] = useState<DrawerMode>("none");
const [exportTab, setExportTab] = useState<ExportTab>("export");
@ -388,7 +390,7 @@ export default function MobileReviewSettingsDrawer({
{t("filter")}
</Button>
)}
{features.includes("debug-replay") && (
{isAdmin && features.includes("debug-replay") && (
<Button
className="flex w-full items-center justify-center gap-2"
aria-label={t("title", { ns: "views/replay" })}

View File

@ -95,9 +95,15 @@ export default function DetailActionsMenu({
),
});
} else {
toast.error(t("dialog.toast.error", { error: errorMessage }), {
position: "top-center",
});
toast.error(
t("dialog.toast.error", {
ns: "views/replay",
error: errorMessage,
}),
{
position: "top-center",
},
);
}
})
.finally(() => {
@ -229,7 +235,7 @@ export default function DetailActionsMenu({
</DropdownMenuItem>
)}
{search.has_clip && (
{isAdmin && search.has_clip && (
<DropdownMenuItem
className="cursor-pointer"
aria-label={t("itemMenu.debugReplay.aria")}

View File

@ -94,9 +94,15 @@ export default function EventMenu({
},
);
} else {
toast.error(t("dialog.toast.error", { error: errorMessage }), {
position: "top-center",
});
toast.error(
t("dialog.toast.error", {
ns: "views/replay",
error: errorMessage,
}),
{
position: "top-center",
},
);
}
})
.finally(() => {
@ -177,7 +183,7 @@ export default function EventMenu({
{t("itemMenu.findSimilar.label")}
</DropdownMenuItem>
)}
{event.has_clip && (
{isAdmin && event.has_clip && (
<DropdownMenuItem
className="cursor-pointer"
disabled={isStarting}

View File

@ -44,6 +44,7 @@ import {
import { IoMdArrowRoundBack } from "react-icons/io";
import { useLocation, useNavigate } from "react-router-dom";
import { Toaster } from "@/components/ui/sonner";
import { useIsAdmin } from "@/hooks/use-is-admin";
import useSWR from "swr";
import { TimeRange, TimelineType } from "@/types/timeline";
import MobileCameraDrawer from "@/components/overlay/MobileCameraDrawer";
@ -109,6 +110,7 @@ export function RecordingView({
}: RecordingViewProps) {
const { t } = useTranslation(["views/events", "components/dialog"]);
const { data: config } = useSWR<FrigateConfig>("config");
const isAdmin = useIsAdmin();
const navigate = useNavigate();
const location = useLocation();
const contentRef = useRef<HTMLDivElement | null>(null);
@ -723,13 +725,17 @@ export function RecordingView({
setCustomShareTimestamp(initialTimestamp);
setShareTimestampOpen(true);
}}
onDebugReplayClick={() => {
setDebugReplayRange({
after: timeRange.before - 60,
before: timeRange.before,
});
setDebugReplayMode("select");
}}
onDebugReplayClick={
isAdmin
? () => {
setDebugReplayRange({
after: timeRange.before - 60,
before: timeRange.before,
});
setDebugReplayMode("select");
}
: undefined
}
onExportClick={() => {
const now = new Date(timeRange.before * 1000);
now.setHours(now.getHours() - 1);