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 = {};
|
const cameraStates: WsState = {};
|
||||||
|
|
||||||
Object.entries(cameraActivity).forEach(([name, state]) => {
|
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
|
// @ts-expect-error we know this is correct
|
||||||
state["config"];
|
state["config"];
|
||||||
cameraStates[`${name}/recordings/state`] = record ? "ON" : "OFF";
|
cameraStates[`${name}/recordings/state`] = record ? "ON" : "OFF";
|
||||||
@ -63,6 +71,8 @@ function useValue(): useValueReturn {
|
|||||||
cameraStates[`${name}/notifications/state`] = notifications
|
cameraStates[`${name}/notifications/state`] = notifications
|
||||||
? "ON"
|
? "ON"
|
||||||
: "OFF";
|
: "OFF";
|
||||||
|
cameraStates[`${name}/notifications/suspended`] =
|
||||||
|
notifications_suspended || 0;
|
||||||
cameraStates[`${name}/ptz_autotracker/state`] = autotracking
|
cameraStates[`${name}/ptz_autotracker/state`] = autotracking
|
||||||
? "ON"
|
? "ON"
|
||||||
: "OFF";
|
: "OFF";
|
||||||
@ -428,6 +438,20 @@ export function useNotifications(camera: string): {
|
|||||||
return { payload: payload as ToggleableSetting, send };
|
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(): {
|
export function useNotificationTest(): {
|
||||||
payload: string;
|
payload: string;
|
||||||
send: (payload: string, retain?: boolean) => void;
|
send: (payload: string, retain?: boolean) => void;
|
||||||
|
|||||||
@ -26,8 +26,19 @@ import { Link } from "react-router-dom";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useNotifications, useNotificationTest } from "@/api/ws";
|
import {
|
||||||
|
useNotifications,
|
||||||
|
useNotificationSuspend,
|
||||||
|
useNotificationTest,
|
||||||
|
} from "@/api/ws";
|
||||||
import FilterSwitch from "@/components/filter/FilterSwitch";
|
import FilterSwitch from "@/components/filter/FilterSwitch";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
|
||||||
const NOTIFICATION_SERVICE_WORKER = "notifications-worker.js";
|
const NOTIFICATION_SERVICE_WORKER = "notifications-worker.js";
|
||||||
|
|
||||||
@ -362,7 +373,7 @@ export default function NotificationView({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{registration != null && (
|
{cameras && (
|
||||||
<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" />
|
||||||
@ -393,18 +404,83 @@ type CameraNotificationSwitchProps = {
|
|||||||
camera: string;
|
camera: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function CameraNotificationSwitch({ camera }: CameraNotificationSwitchProps) {
|
export function CameraNotificationSwitch({
|
||||||
|
camera,
|
||||||
|
}: CameraNotificationSwitchProps) {
|
||||||
const { payload: notificationState, send: sendNotification } =
|
const { payload: notificationState, send: sendNotification } =
|
||||||
useNotifications(camera);
|
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 (
|
return (
|
||||||
<FilterSwitch
|
<div className="flex flex-col">
|
||||||
key={camera}
|
<div className="flex items-center justify-between">
|
||||||
isChecked={notificationState == "ON"}
|
<FilterSwitch
|
||||||
label={camera.replaceAll("_", " ")}
|
key={camera}
|
||||||
onCheckedChange={(isChecked) => {
|
isChecked={isOn}
|
||||||
sendNotification(isChecked ? "ON" : "OFF");
|
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