mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-08-22 13:47:29 +02:00
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:
parent
95cea06dd3
commit
8f4b5b4bdb
@ -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);
|
||||||
|
@ -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" />
|
||||||
|
Loading…
Reference in New Issue
Block a user