UI viewer role (#16978)

* db migration

* db model

* assign admin role on password reset

* add role to jwt and api responses

* don't restrict api access for admins yet

* use json response

* frontend auth context

* update auth form for profile endpoint

* add access denied page

* add protected routes

* auth hook

* dialogs

* user settings view

* restrict viewer access to settings

* restrict camera functions for viewer role

* add password dialog to account menu

* spacing tweak

* migrator default to admin

* escape quotes in migrator

* ui tweaks

* tweaks

* colors

* colors

* fix merge conflict

* fix icons

* add api layer enforcement

* ui tweaks

* fix error message

* debug

* clean up

* remove print

* guard apis for admin only

* fix tests

* fix review tests

* use correct error responses from api in toasts

* add role to account menu
This commit is contained in:
Josh Hawkins
2025-03-08 10:01:08 -06:00
committed by GitHub
parent 6f9d9cd5a8
commit 74ca009b0b
50 changed files with 1951 additions and 732 deletions
+23 -3
View File
@@ -40,6 +40,7 @@ import UiSettingsView from "@/views/settings/UiSettingsView";
import { useSearchEffect } from "@/hooks/use-overlay-state";
import { useSearchParams } from "react-router-dom";
import { useInitialCameraState } from "@/api/ws";
import { useIsAdmin } from "@/hooks/use-is-admin";
const allSettingsViews = [
"UI settings",
@@ -62,6 +63,15 @@ export default function Settings() {
const [searchParams] = useSearchParams();
// auth and roles
const isAdmin = useIsAdmin();
const allowedViewsForViewer: SettingsType[] = ["UI settings", "debug"];
const visibleSettingsViews = !isAdmin
? allowedViewsForViewer
: allSettingsViews;
// TODO: confirm leave page
const [unsavedChanges, setUnsavedChanges] = useState(false);
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
@@ -149,7 +159,12 @@ export default function Settings() {
useSearchEffect("page", (page: string) => {
if (allSettingsViews.includes(page as SettingsType)) {
setPage(page as SettingsType);
// Restrict viewer to UI settings
if (!isAdmin && !["UI settings", "debug"].includes(page)) {
setPage("UI settings");
} else {
setPage(page as SettingsType);
}
}
// don't clear url params if we're creating a new object mask
return !searchParams.has("object_mask");
@@ -180,11 +195,16 @@ export default function Settings() {
value={pageToggle}
onValueChange={(value: SettingsType) => {
if (value) {
setPageToggle(value);
// Restrict viewer navigation
if (!isAdmin && !["UI settings", "debug"].includes(value)) {
setPageToggle("UI settings");
} else {
setPageToggle(value);
}
}
}}
>
{Object.values(allSettingsViews).map((item) => (
{visibleSettingsViews.map((item) => (
<ToggleGroupItem
key={item}
className={`flex scroll-mx-10 items-center justify-between gap-2 ${page == "UI settings" ? "last:mr-20" : ""} ${pageToggle == item ? "" : "*:text-muted-foreground"}`}