blakeblackshear.frigate/web/src/components/settings/General.tsx
Blake Blackshear 1133202cbd
Auth! (#11347)
* reload the window on 401

* backend apis for auth

* add login page

* re-enable web linter

* fix login page routing

* bypass csrf for internal auth endpoint

* disable healthcheck in devcontainer target

* include login page in vite build

* redirect to login page on 401

* implement config for users and settings

* implement JWT actual secret

* add brute force protection on login

* add support for redirecting from auth failures on api calls

* return location for redirect

* default cookie name should pass regex test

* set hash iterations to current OWASP recommendation

* move users to database instead of config

* config option to reset admin password on startup

* user management UI

* check for deleted user on refresh

* validate username and fixes

* remove password constraint

* cleanup

* fix user check on refresh

* web fixes

* implement auth via new external port

* use x-forwarded-for to rate limit login attempts by ip

* implement logout and profile

* fixes

* lint fixes

* add support for user passthru from upstream proxies

* add support for specifying a logout url

* add documentation

* Update docs/docs/configuration/authentication.md

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>

* Update docs/docs/configuration/authentication.md

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2024-05-18 10:36:13 -06:00

136 lines
4.7 KiB
TypeScript

import Heading from "@/components/ui/heading";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { useCallback, useEffect } from "react";
import { Toaster } from "sonner";
import { toast } from "sonner";
import { Separator } from "../ui/separator";
import { Button } from "../ui/button";
import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import { del as delData } from "idb-keyval";
import { usePersistence } from "@/hooks/use-persistence";
import { isSafari } from "react-device-detect";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
} from "../ui/select";
const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16];
export default function General() {
const { data: config } = useSWR<FrigateConfig>("config");
const clearStoredLayouts = useCallback(() => {
if (!config) {
return [];
}
Object.entries(config.camera_groups).forEach(async (value) => {
await delData(`${value[0]}-draggable-layout`)
.then(() => {
toast.success(`Cleared stored layout for ${value[0]}`, {
position: "top-center",
});
})
.catch((error) => {
toast.error(
`Failed to clear stored layout: ${error.response.data.message}`,
{ position: "top-center" },
);
});
});
}, [config]);
useEffect(() => {
document.title = "General Settings - Frigate";
}, []);
const [playbackRate, setPlaybackRate] = usePersistence("playbackRate", 1);
return (
<>
<div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} />
<div className="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">
<Heading as="h3" className="my-2">
General Settings
</Heading>
<div className="flex w-full flex-col space-y-6">
<div className="mt-2 space-y-6">
<div className="space-y-0.5">
<div className="text-md">Stored Layouts</div>
<div className="my-2 text-sm text-muted-foreground">
<p>
The layout of cameras in a camera group can be
dragged/resized. The positions are stored in your browser's
local storage.
</p>
</div>
</div>
<div className="flex flex-row items-center justify-start gap-2">
<Button onClick={clearStoredLayouts}>Clear All Layouts</Button>
</div>
</div>
<Separator className="my-2 flex bg-secondary" />
<div className="mt-2 space-y-6">
<div className="space-y-0.5">
<div className="text-md">Default Playback Rate</div>
<div className="my-2 text-sm text-muted-foreground">
<p>Default playback rate for recordings playback.</p>
</div>
</div>
</div>
<Select
value={playbackRate?.toString()}
onValueChange={(value) => setPlaybackRate(parseFloat(value))}
>
<SelectTrigger className="w-20">
{`${playbackRate}x`}
</SelectTrigger>
<SelectContent>
<SelectGroup>
{PLAYBACK_RATE_DEFAULT.map((rate) => (
<SelectItem
key={rate}
className="cursor-pointer"
value={rate.toString()}
>
{rate}x
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Separator className="my-2 flex bg-secondary" />
<div className="mt-2 space-y-6">
<div className="space-y-0.5">
<div className="text-md">Low Data Mode</div>
<div className="my-2 text-sm text-muted-foreground">
<p>
Not yet implemented. <em>Default: disabled</em>
</p>
</div>
</div>
<div className="flex flex-row items-center justify-start gap-2">
<Switch
id="lowdata"
checked={false}
onCheckedChange={() => {}}
/>
<Label htmlFor="lowdata">
Low Data Mode (this device only)
</Label>
</div>
</div>
</div>
</div>
</div>
</>
);
}