mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-18 09:04:28 +03:00
frontend
This commit is contained in:
parent
b9f70b2e4a
commit
ff270d995c
@ -53,7 +53,15 @@ function useValue(): useValueReturn {
|
||||
const cameraStates: WsState = {};
|
||||
|
||||
Object.entries(cameraActivity).forEach(([name, state]) => {
|
||||
const { record, detect, snapshots, audio, notifications, autotracking } =
|
||||
const {
|
||||
record,
|
||||
detect,
|
||||
snapshots,
|
||||
audio,
|
||||
notifications,
|
||||
notifications_suspended,
|
||||
autotracking,
|
||||
} =
|
||||
// @ts-expect-error we know this is correct
|
||||
state["config"];
|
||||
cameraStates[`${name}/recordings/state`] = record ? "ON" : "OFF";
|
||||
@ -63,6 +71,8 @@ function useValue(): useValueReturn {
|
||||
cameraStates[`${name}/notifications/state`] = notifications
|
||||
? "ON"
|
||||
: "OFF";
|
||||
cameraStates[`${name}/notifications/suspended`] =
|
||||
notifications_suspended || 0;
|
||||
cameraStates[`${name}/ptz_autotracker/state`] = autotracking
|
||||
? "ON"
|
||||
: "OFF";
|
||||
@ -428,6 +438,20 @@ export function useNotifications(camera: string): {
|
||||
return { payload: payload as ToggleableSetting, send };
|
||||
}
|
||||
|
||||
export function useNotificationSuspend(camera: string): {
|
||||
payload: string;
|
||||
send: (payload: string, retain?: boolean) => void;
|
||||
} {
|
||||
const {
|
||||
value: { payload },
|
||||
send,
|
||||
} = useWs(
|
||||
`${camera}/notifications/suspended`,
|
||||
`${camera}/notifications/suspend`,
|
||||
);
|
||||
return { payload: payload as string, send };
|
||||
}
|
||||
|
||||
export function useNotificationTest(): {
|
||||
payload: string;
|
||||
send: (payload: string, retain?: boolean) => void;
|
||||
|
||||
@ -26,8 +26,19 @@ import { Link } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import useSWR from "swr";
|
||||
import { z } from "zod";
|
||||
import { useNotifications, useNotificationTest } from "@/api/ws";
|
||||
import {
|
||||
useNotifications,
|
||||
useNotificationSuspend,
|
||||
useNotificationTest,
|
||||
} from "@/api/ws";
|
||||
import FilterSwitch from "@/components/filter/FilterSwitch";
|
||||
import {
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
const NOTIFICATION_SERVICE_WORKER = "notifications-worker.js";
|
||||
|
||||
@ -362,7 +373,7 @@ export default function NotificationView({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{registration != null && (
|
||||
{cameras && (
|
||||
<div className="mt-4 gap-2 space-y-6">
|
||||
<div className="space-y-3">
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
@ -393,18 +404,83 @@ type CameraNotificationSwitchProps = {
|
||||
camera: string;
|
||||
};
|
||||
|
||||
function CameraNotificationSwitch({ camera }: CameraNotificationSwitchProps) {
|
||||
export function CameraNotificationSwitch({
|
||||
camera,
|
||||
}: CameraNotificationSwitchProps) {
|
||||
const { payload: notificationState, send: sendNotification } =
|
||||
useNotifications(camera);
|
||||
const { payload: notificationSuspendUntil, send: sendNotificationSuspend } =
|
||||
useNotificationSuspend(camera);
|
||||
const [isSuspended, setIsSuspended] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (notificationSuspendUntil) {
|
||||
setIsSuspended(notificationSuspendUntil != "0");
|
||||
}
|
||||
}, [notificationSuspendUntil]);
|
||||
|
||||
const handleSuspend = (duration: string) => {
|
||||
sendNotificationSuspend(duration);
|
||||
};
|
||||
|
||||
const handleCancelSuspension = () => {
|
||||
sendNotificationSuspend("0"); // Assuming sending "0" cancels the suspension
|
||||
};
|
||||
|
||||
const formatSuspendedUntil = (timestamp: string) => {
|
||||
const date = new Date(parseInt(timestamp) * 1000);
|
||||
return date.toLocaleString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
const isOn = notificationState === "ON" || isSuspended;
|
||||
|
||||
return (
|
||||
<FilterSwitch
|
||||
key={camera}
|
||||
isChecked={notificationState == "ON"}
|
||||
label={camera.replaceAll("_", " ")}
|
||||
onCheckedChange={(isChecked) => {
|
||||
sendNotification(isChecked ? "ON" : "OFF");
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center justify-between">
|
||||
<FilterSwitch
|
||||
key={camera}
|
||||
isChecked={isOn}
|
||||
label={camera.replaceAll("_", " ")}
|
||||
onCheckedChange={(isChecked) => {
|
||||
sendNotification(isChecked ? "ON" : "OFF");
|
||||
}}
|
||||
/>
|
||||
{!isSuspended && isOn && (
|
||||
<Select onValueChange={handleSuspend}>
|
||||
<SelectTrigger className="w-auto">
|
||||
<SelectValue placeholder="Suspend" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="30">Suspend for 30 minutes</SelectItem>
|
||||
<SelectItem value="60">Suspend for 1 hour</SelectItem>
|
||||
<SelectItem value="180">Suspend for 3 hours</SelectItem>
|
||||
<SelectItem value="360">Suspend for 6 hours</SelectItem>
|
||||
<SelectItem value="840">Suspend for 12 hours</SelectItem>
|
||||
<SelectItem value="1440">Suspend for 24 hours</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
{isSuspended && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={handleCancelSuspension}
|
||||
>
|
||||
Cancel Suspension
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isSuspended && notificationSuspendUntil && (
|
||||
<div className="mt-1 text-sm text-muted-foreground">
|
||||
Suspended until {formatSuspendedUntil(notificationSuspendUntil)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user