Show dialog when restarting from config editor (#14815)

* Show restart dialog when restarting from config editor

* don't save until confirmed restart
This commit is contained in:
Josh Hawkins 2024-11-05 09:33:41 -06:00 committed by GitHub
parent 404807c697
commit fc0fb158d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 141 additions and 106 deletions

View File

@ -24,7 +24,7 @@ import {
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "../ui/dropdown-menu"; } from "../ui/dropdown-menu";
import { Button } from "../ui/button";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { CgDarkMode } from "react-icons/cg"; import { CgDarkMode } from "react-icons/cg";
import { import {
@ -33,30 +33,15 @@ import {
useTheme, useTheme,
} from "@/context/theme-provider"; } from "@/context/theme-provider";
import { IoColorPalette } from "react-icons/io5"; import { IoColorPalette } from "react-icons/io5";
import {
AlertDialog, import { useState } from "react";
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "../ui/alert-dialog";
import { useEffect, useState } from "react";
import { useRestart } from "@/api/ws"; import { useRestart } from "@/api/ws";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "../ui/sheet";
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import ActivityIndicator from "../indicators/activity-indicator";
import { isDesktop, isMobile } from "react-device-detect"; import { isDesktop, isMobile } from "react-device-detect";
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
import { import {
@ -68,8 +53,8 @@ import {
} from "../ui/dialog"; } from "../ui/dialog";
import { TooltipPortal } from "@radix-ui/react-tooltip"; import { TooltipPortal } from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { baseUrl } from "@/api/baseUrl";
import useSWR from "swr"; import useSWR from "swr";
import RestartDialog from "../overlay/dialog/RestartDialog";
type GeneralSettingsProps = { type GeneralSettingsProps = {
className?: string; className?: string;
@ -83,35 +68,8 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
const { theme, colorScheme, setTheme, setColorScheme } = useTheme(); const { theme, colorScheme, setTheme, setColorScheme } = useTheme();
const [restartDialogOpen, setRestartDialogOpen] = useState(false); const [restartDialogOpen, setRestartDialogOpen] = useState(false);
const [restartingSheetOpen, setRestartingSheetOpen] = useState(false);
const [countdown, setCountdown] = useState(60);
const { send: sendRestart } = useRestart(); const { send: sendRestart } = useRestart();
useEffect(() => {
let countdownInterval: NodeJS.Timeout;
if (restartingSheetOpen) {
countdownInterval = setInterval(() => {
setCountdown((prevCountdown) => prevCountdown - 1);
}, 1000);
}
return () => {
clearInterval(countdownInterval);
};
}, [restartingSheetOpen]);
useEffect(() => {
if (countdown === 0) {
window.location.href = baseUrl;
}
}, [countdown]);
const handleForceReload = () => {
window.location.href = baseUrl;
};
const Container = isDesktop ? DropdownMenu : Drawer; const Container = isDesktop ? DropdownMenu : Drawer;
const Trigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger; const Trigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger;
const Content = isDesktop ? DropdownMenuContent : DrawerContent; const Content = isDesktop ? DropdownMenuContent : DrawerContent;
@ -413,64 +371,11 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
</div> </div>
</Content> </Content>
</Container> </Container>
{restartDialogOpen && ( <RestartDialog
<AlertDialog isOpen={restartDialogOpen}
open={restartDialogOpen} onClose={() => setRestartDialogOpen(false)}
onOpenChange={() => setRestartDialogOpen(false)} onRestart={() => sendRestart("restart")}
> />
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want to restart Frigate?
</AlertDialogTitle>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
setRestartingSheetOpen(true);
sendRestart("restart");
}}
>
Restart
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
{restartingSheetOpen && (
<>
<Sheet
open={restartingSheetOpen}
onOpenChange={() => setRestartingSheetOpen(false)}
>
<SheetContent
side="top"
onInteractOutside={(e) => e.preventDefault()}
>
<div className="flex flex-col items-center">
<ActivityIndicator />
<SheetHeader className="mt-5 text-center">
<SheetTitle className="text-center">
Frigate is Restarting
</SheetTitle>
<SheetDescription className="text-center">
<p>This page will reload in {countdown} seconds.</p>
</SheetDescription>
</SheetHeader>
<Button
size="lg"
className="mt-5"
aria-label="Force reload now"
onClick={handleForceReload}
>
Force Reload Now
</Button>
</div>
</SheetContent>
</Sheet>
</>
)}
</> </>
); );
} }

View File

@ -0,0 +1,122 @@
import { useState, useEffect } from "react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetDescription,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import { baseUrl } from "@/api/baseUrl";
type RestartDialogProps = {
isOpen: boolean;
onClose: () => void;
onRestart: () => void;
};
export default function RestartDialog({
isOpen,
onClose,
onRestart,
}: RestartDialogProps) {
const [restartDialogOpen, setRestartDialogOpen] = useState(isOpen);
const [restartingSheetOpen, setRestartingSheetOpen] = useState(false);
const [countdown, setCountdown] = useState(60);
useEffect(() => {
setRestartDialogOpen(isOpen);
}, [isOpen]);
useEffect(() => {
let countdownInterval: NodeJS.Timeout;
if (restartingSheetOpen) {
countdownInterval = setInterval(() => {
setCountdown((prevCountdown) => prevCountdown - 1);
}, 1000);
}
return () => {
clearInterval(countdownInterval);
};
}, [restartingSheetOpen]);
useEffect(() => {
if (countdown === 0) {
window.location.href = baseUrl;
}
}, [countdown]);
const handleRestart = () => {
setRestartingSheetOpen(true);
onRestart();
};
const handleForceReload = () => {
window.location.href = baseUrl;
};
return (
<>
<AlertDialog
open={restartDialogOpen}
onOpenChange={() => {
setRestartDialogOpen(false);
onClose();
}}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want to restart Frigate?
</AlertDialogTitle>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleRestart}>
Restart
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Sheet
open={restartingSheetOpen}
onOpenChange={() => setRestartingSheetOpen(false)}
>
<SheetContent side="top" onInteractOutside={(e) => e.preventDefault()}>
<div className="flex flex-col items-center">
<ActivityIndicator />
<SheetHeader className="mt-5 text-center">
<SheetTitle className="text-center">
Frigate is Restarting
</SheetTitle>
<SheetDescription className="text-center">
<div>This page will reload in {countdown} seconds.</div>
</SheetDescription>
</SheetHeader>
<Button
size="lg"
className="mt-5"
aria-label="Force reload now"
onClick={handleForceReload}
>
Force Reload Now
</Button>
</div>
</SheetContent>
</Sheet>
</>
);
}

View File

@ -13,6 +13,7 @@ import { Toaster } from "@/components/ui/sonner";
import { toast } from "sonner"; import { toast } from "sonner";
import { LuCopy, LuSave } from "react-icons/lu"; import { LuCopy, LuSave } from "react-icons/lu";
import { MdOutlineRestartAlt } from "react-icons/md"; import { MdOutlineRestartAlt } from "react-icons/md";
import RestartDialog from "@/components/overlay/dialog/RestartDialog";
type SaveOptions = "saveonly" | "restart"; type SaveOptions = "saveonly" | "restart";
@ -33,6 +34,8 @@ function ConfigEditor() {
const configRef = useRef<HTMLDivElement | null>(null); const configRef = useRef<HTMLDivElement | null>(null);
const schemaConfiguredRef = useRef(false); const schemaConfiguredRef = useRef(false);
const [restartDialogOpen, setRestartDialogOpen] = useState(false);
const onHandleSaveConfig = useCallback( const onHandleSaveConfig = useCallback(
async (save_option: SaveOptions) => { async (save_option: SaveOptions) => {
if (!editorRef.current) { if (!editorRef.current) {
@ -202,7 +205,7 @@ function ConfigEditor() {
size="sm" size="sm"
className="flex items-center gap-2" className="flex items-center gap-2"
aria-label="Save and restart" aria-label="Save and restart"
onClick={() => onHandleSaveConfig("restart")} onClick={() => setRestartDialogOpen(true)}
> >
<div className="relative size-5"> <div className="relative size-5">
<LuSave className="absolute left-0 top-0 size-3 text-secondary-foreground" /> <LuSave className="absolute left-0 top-0 size-3 text-secondary-foreground" />
@ -231,6 +234,11 @@ function ConfigEditor() {
<div ref={configRef} className="mt-2 h-[calc(100%-2.75rem)]" /> <div ref={configRef} className="mt-2 h-[calc(100%-2.75rem)]" />
</div> </div>
<Toaster closeButton={true} /> <Toaster closeButton={true} />
<RestartDialog
isOpen={restartDialogOpen}
onClose={() => setRestartDialogOpen(false)}
onRestart={() => onHandleSaveConfig("restart")}
/>
</div> </div>
); );
} }