This commit is contained in:
Josh Hawkins 2026-03-05 10:16:17 -06:00
parent 5e36dd0776
commit fdf704fffd
3 changed files with 136 additions and 10 deletions

View File

@ -40,7 +40,8 @@ import UsersView from "@/views/settings/UsersView";
import RolesView from "@/views/settings/RolesView";
import UiSettingsView from "@/views/settings/UiSettingsView";
import FrigatePlusSettingsView from "@/views/settings/FrigatePlusSettingsView";
import MaintenanceSettingsView from "@/views/settings/MaintenanceSettingsView";
import MediaSyncSettingsView from "@/views/settings/MediaSyncSettingsView";
import RegionGridSettingsView from "@/views/settings/RegionGridSettingsView";
import SystemDetectionModelSettingsView from "@/views/settings/SystemDetectionModelSettingsView";
import {
SingleSectionPage,
@ -154,7 +155,8 @@ const allSettingsViews = [
"roles",
"notifications",
"frigateplus",
"maintenance",
"mediaSync",
"regionGrid",
] as const;
type SettingsType = (typeof allSettingsViews)[number];
@ -444,7 +446,10 @@ const settingsGroups = [
},
{
label: "maintenance",
items: [{ key: "maintenance", component: MaintenanceSettingsView }],
items: [
{ key: "mediaSync", component: MediaSyncSettingsView },
{ key: "regionGrid", component: RegionGridSettingsView },
],
},
];
@ -471,15 +476,12 @@ const CAMERA_SELECT_BUTTON_PAGES = [
"masksAndZones",
"motionTuner",
"triggers",
"regionGrid",
];
const ALLOWED_VIEWS_FOR_VIEWER = ["ui", "debug", "notifications"];
const LARGE_BOTTOM_MARGIN_PAGES = [
"masksAndZones",
"motionTuner",
"maintenance",
];
const LARGE_BOTTOM_MARGIN_PAGES = ["masksAndZones", "motionTuner", "mediaSync"];
// keys for camera sections
const CAMERA_SECTION_MAPPING: Record<string, SettingsType> = {

View File

@ -15,7 +15,7 @@ import { cn } from "@/lib/utils";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import { MediaSyncStats } from "@/types/ws";
export default function MaintenanceSettingsView() {
export default function MediaSyncSettingsView() {
const { t } = useTranslation("views/settings");
const [selectedMediaTypes, setSelectedMediaTypes] = useState<string[]>([
"all",
@ -103,7 +103,7 @@ export default function MaintenanceSettingsView() {
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto px-2 md:order-none">
<div className="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
<div className="col-span-1">
<Heading as="h4" className="mb-2">
<Heading as="h4" className="mb-2 hidden md:block">
{t("maintenance.sync.title")}
</Heading>

View File

@ -0,0 +1,124 @@
import Heading from "@/components/ui/heading";
import { Button, buttonVariants } from "@/components/ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Toaster } from "@/components/ui/sonner";
import { useCallback, useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import axios from "axios";
import { toast } from "sonner";
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
import { cn } from "@/lib/utils";
type RegionGridSettingsViewProps = {
selectedCamera: string;
};
export default function RegionGridSettingsView({
selectedCamera,
}: RegionGridSettingsViewProps) {
const { t } = useTranslation("views/settings");
const { addMessage } = useContext(StatusBarMessagesContext)!;
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
const [isClearing, setIsClearing] = useState(false);
const [imageKey, setImageKey] = useState(0);
const handleClear = useCallback(async () => {
setIsClearing(true);
try {
await axios.delete(`${selectedCamera}/region_grid`);
toast.success(t("maintenance.regionGrid.clearSuccess"), {
position: "top-center",
});
setImageKey((prev) => prev + 1);
addMessage(
"region_grid_restart",
t("maintenance.regionGrid.restartRequired"),
undefined,
"region_grid_settings",
);
} catch {
toast.error(t("maintenance.regionGrid.clearError"), {
position: "top-center",
});
} finally {
setIsClearing(false);
setIsConfirmOpen(false);
}
}, [selectedCamera, t, addMessage]);
return (
<>
<div className="flex size-full flex-col pb-12 md:flex-row">
<Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto px-2 md:order-none">
<Heading as="h4" className="mb-2 hidden md:block">
{t("maintenance.regionGrid.title")}
</Heading>
<div className="max-w-6xl">
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-muted-foreground">
<p>{t("maintenance.regionGrid.desc")}</p>
</div>
</div>
<div className="mb-4 max-w-5xl rounded-lg border border-secondary">
<img
key={imageKey}
src={`api/${selectedCamera}/grid.jpg?cache=${imageKey}`}
alt={t("maintenance.regionGrid.title")}
className="w-full"
/>
</div>
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[50%]">
<Button
onClick={() => setIsConfirmOpen(true)}
disabled={isClearing}
variant="destructive"
className="flex flex-1 text-white md:max-w-sm"
>
{t("maintenance.regionGrid.clear")}
</Button>
</div>
</div>
</div>
<AlertDialog open={isConfirmOpen} onOpenChange={setIsConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("maintenance.regionGrid.clearConfirmTitle")}
</AlertDialogTitle>
<AlertDialogDescription>
{t("maintenance.regionGrid.clearConfirmDesc")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
{t("button.cancel", { ns: "common" })}
</AlertDialogCancel>
<AlertDialogAction
className={cn(
buttonVariants({ variant: "destructive" }),
"text-white",
)}
onClick={handleClear}
>
{t("maintenance.regionGrid.clear")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
}