Refactored Viewer role Notifications settings (#19640)

- now each individual element is shown if allowed by role, instead of having multiple return statement for each role
This commit is contained in:
Jan Šuklje 2025-08-20 02:29:11 +02:00 committed by GitHub
parent 95cea06dd3
commit 8f4b5b4bdb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 150 additions and 123 deletions

View File

@ -73,7 +73,11 @@ export default function Settings() {
const isAdmin = useIsAdmin(); const isAdmin = useIsAdmin();
const allowedViewsForViewer: SettingsType[] = ["ui", "debug"]; const allowedViewsForViewer: SettingsType[] = [
"ui",
"debug",
"notifications",
];
const visibleSettingsViews = !isAdmin const visibleSettingsViews = !isAdmin
? allowedViewsForViewer ? allowedViewsForViewer
: allSettingsViews; : allSettingsViews;
@ -164,7 +168,7 @@ export default function Settings() {
useSearchEffect("page", (page: string) => { useSearchEffect("page", (page: string) => {
if (allSettingsViews.includes(page as SettingsType)) { if (allSettingsViews.includes(page as SettingsType)) {
// Restrict viewer to UI settings // Restrict viewer to UI settings
if (!isAdmin && !["ui", "debug"].includes(page)) { if (!isAdmin && !allowedViewsForViewer.includes(page as SettingsType)) {
setPage("ui"); setPage("ui");
} else { } else {
setPage(page as SettingsType); setPage(page as SettingsType);
@ -200,7 +204,7 @@ export default function Settings() {
onValueChange={(value: SettingsType) => { onValueChange={(value: SettingsType) => {
if (value) { if (value) {
// Restrict viewer navigation // Restrict viewer navigation
if (!isAdmin && !["ui", "debug"].includes(value)) { if (!isAdmin && !allowedViewsForViewer.includes(value)) {
setPageToggle("ui"); setPageToggle("ui");
} else { } else {
setPageToggle(value); setPageToggle(value);

View File

@ -46,6 +46,8 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { useDateLocale } from "@/hooks/use-date-locale"; import { useDateLocale } from "@/hooks/use-date-locale";
import { useDocDomain } from "@/hooks/use-doc-domain"; import { useDocDomain } from "@/hooks/use-doc-domain";
import { useIsAdmin } from "@/hooks/use-is-admin";
import { cn } from "@/lib/utils";
const NOTIFICATION_SERVICE_WORKER = "notifications-worker.js"; const NOTIFICATION_SERVICE_WORKER = "notifications-worker.js";
@ -64,6 +66,10 @@ export default function NotificationView({
const { t } = useTranslation(["views/settings"]); const { t } = useTranslation(["views/settings"]);
const { getLocaleDocUrl } = useDocDomain(); const { getLocaleDocUrl } = useDocDomain();
// roles
const isAdmin = useIsAdmin();
const { data: config, mutate: updateConfig } = useSWR<FrigateConfig>( const { data: config, mutate: updateConfig } = useSWR<FrigateConfig>(
"config", "config",
{ {
@ -380,7 +386,11 @@ export default function NotificationView({
<div className="flex size-full flex-col md:flex-row"> <div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0"> <div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
<div className="grid w-full grid-cols-1 gap-4 md:grid-cols-2"> <div
className={cn(
isAdmin && "grid w-full grid-cols-1 gap-4 md:grid-cols-2",
)}
>
<div className="col-span-1"> <div className="col-span-1">
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
{t("notification.notificationSettings.title")} {t("notification.notificationSettings.title")}
@ -403,138 +413,151 @@ export default function NotificationView({
</div> </div>
</div> </div>
<Form {...form}> {isAdmin && (
<form <Form {...form}>
onSubmit={form.handleSubmit(onSubmit)} <form
className="mt-2 space-y-6" onSubmit={form.handleSubmit(onSubmit)}
> className="mt-2 space-y-6"
<FormField >
control={form.control} <FormField
name="email" control={form.control}
render={({ field }) => ( name="email"
<FormItem> render={({ field }) => (
<FormLabel>{t("notification.email.title")}</FormLabel> <FormItem>
<FormControl> <FormLabel>{t("notification.email.title")}</FormLabel>
<Input <FormControl>
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark] md:w-72" <Input
placeholder={t("notification.email.placeholder")} className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark] md:w-72"
{...field} placeholder={t("notification.email.placeholder")}
/> {...field}
</FormControl> />
<FormDescription> </FormControl>
{t("notification.email.desc")} <FormDescription>
</FormDescription> {t("notification.email.desc")}
<FormMessage /> </FormDescription>
</FormItem> <FormMessage />
)} </FormItem>
/> )}
/>
<FormField <FormField
control={form.control} control={form.control}
name="cameras" name="cameras"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
{allCameras && allCameras?.length > 0 ? ( {allCameras && allCameras?.length > 0 ? (
<> <>
<div className="mb-2"> <div className="mb-2">
<FormLabel className="flex flex-row items-center text-base"> <FormLabel className="flex flex-row items-center text-base">
{t("notification.cameras.title")} {t("notification.cameras.title")}
</FormLabel> </FormLabel>
</div> </div>
<div className="max-w-md space-y-2 rounded-lg bg-secondary p-4"> <div className="max-w-md space-y-2 rounded-lg bg-secondary p-4">
<FormField <FormField
control={form.control} control={form.control}
name="allEnabled" name="allEnabled"
render={({ field }) => ( render={({ field }) => (
<FilterSwitch
label={t("cameras.all.title", {
ns: "components/filter",
})}
isChecked={field.value}
onCheckedChange={(checked) => {
setChangedValue(true);
if (checked) {
form.setValue("cameras", []);
}
field.onChange(checked);
}}
/>
)}
/>
{allCameras?.map((camera) => (
<FilterSwitch <FilterSwitch
label={t("cameras.all.title", { key={camera.name}
ns: "components/filter", label={camera.name.replaceAll("_", " ")}
})} isChecked={field.value?.includes(
isChecked={field.value} camera.name,
)}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
setChangedValue(true); setChangedValue(true);
let newCameras;
if (checked) { if (checked) {
form.setValue("cameras", []); newCameras = [
...field.value,
camera.name,
];
} else {
newCameras = field.value?.filter(
(value) => value !== camera.name,
);
} }
field.onChange(checked); field.onChange(newCameras);
form.setValue("allEnabled", false);
}} }}
/> />
)} ))}
/> </div>
{allCameras?.map((camera) => ( </>
<FilterSwitch ) : (
key={camera.name} <div className="font-normal text-destructive">
label={camera.name.replaceAll("_", " ")} {t("notification.cameras.noCameras")}
isChecked={field.value?.includes(camera.name)}
onCheckedChange={(checked) => {
setChangedValue(true);
let newCameras;
if (checked) {
newCameras = [
...field.value,
camera.name,
];
} else {
newCameras = field.value?.filter(
(value) => value !== camera.name,
);
}
field.onChange(newCameras);
form.setValue("allEnabled", false);
}}
/>
))}
</div> </div>
</> )}
) : (
<div className="font-normal text-destructive">
{t("notification.cameras.noCameras")}
</div>
)}
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
{t("notification.cameras.desc")} {t("notification.cameras.desc")}
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)}
/>
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[50%]">
<Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })}
onClick={onCancel}
type="button"
>
{t("button.cancel", { ns: "common" })}
</Button>
<Button
variant="select"
disabled={isLoading}
className="flex flex-1"
aria-label={t("button.save", { ns: "common" })}
type="submit"
>
{isLoading ? (
<div className="flex flex-row items-center gap-2">
<ActivityIndicator />
<span>{t("button.saving", { ns: "common" })}</span>
</div>
) : (
t("button.save", { ns: "common" })
)} )}
</Button> />
</div>
</form> <div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[50%]">
</Form> <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })}
onClick={onCancel}
type="button"
>
{t("button.cancel", { ns: "common" })}
</Button>
<Button
variant="select"
disabled={isLoading}
className="flex flex-1"
aria-label={t("button.save", { ns: "common" })}
type="submit"
>
{isLoading ? (
<div className="flex flex-row items-center gap-2">
<ActivityIndicator />
<span>{t("button.saving", { ns: "common" })}</span>
</div>
) : (
t("button.save", { ns: "common" })
)}
</Button>
</div>
</form>
</Form>
)}
</div> </div>
<div className="col-span-1"> <div className="col-span-1">
<div className="mt-4 gap-2 space-y-6"> <div className="mt-4 gap-2 space-y-6">
<div className="flex flex-col gap-2 md:max-w-[50%]"> <div
<Separator className="my-2 flex bg-secondary md:hidden" /> className={cn(
<Heading as="h4" className="my-2"> isAdmin && "flex flex-col gap-2 md:max-w-[50%]",
)}
>
<Separator
className={cn(
"my-2 flex bg-secondary",
isAdmin && "md:hidden",
)}
/>
<Heading as="h4" className={cn(isAdmin ? "my-2" : "my-4")}>
{t("notification.deviceSpecific")} {t("notification.deviceSpecific")}
</Heading> </Heading>
<Button <Button
@ -580,7 +603,7 @@ export default function NotificationView({
? t("notification.unregisterDevice") ? t("notification.unregisterDevice")
: t("notification.registerDevice")} : t("notification.registerDevice")}
</Button> </Button>
{registration != null && registration.active && ( {isAdmin && registration != null && registration.active && (
<Button <Button
aria-label={t("notification.sendTestNotification")} aria-label={t("notification.sendTestNotification")}
onClick={() => sendTestNotification("notification_test")} onClick={() => sendTestNotification("notification_test")}
@ -590,7 +613,7 @@ export default function NotificationView({
)} )}
</div> </div>
</div> </div>
{notificationCameras.length > 0 && ( {isAdmin && notificationCameras.length > 0 && (
<div className="mt-4 gap-2 space-y-6"> <div className="mt-4 gap-2 space-y-6">
<div className="space-y-3"> <div className="space-y-3">
<Separator className="my-2 flex bg-secondary" /> <Separator className="my-2 flex bg-secondary" />