This commit is contained in:
Josh Hawkins 2024-12-17 11:23:55 -06:00
parent b9f70b2e4a
commit ff270d995c
2 changed files with 112 additions and 12 deletions

View File

@ -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;

View File

@ -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>
); );
} }