add restart button to toast when restart is required

This commit is contained in:
Josh Hawkins 2026-02-06 09:35:07 -06:00
parent 5d518bfa65
commit ef55d90379

View File

@ -55,6 +55,8 @@ import { applySchemaDefaults } from "@/lib/config-schema";
import { isJsonObject } from "@/lib/utils"; import { isJsonObject } from "@/lib/utils";
import { ConfigSectionData, JsonObject, JsonValue } from "@/types/configForm"; import { ConfigSectionData, JsonObject, JsonValue } from "@/types/configForm";
import ActivityIndicator from "@/components/indicators/activity-indicator"; import ActivityIndicator from "@/components/indicators/activity-indicator";
import RestartDialog from "@/components/overlay/dialog/RestartDialog";
import { useRestart } from "@/api/ws";
export interface SectionConfig { export interface SectionConfig {
/** Field ordering within the section */ /** Field ordering within the section */
@ -178,8 +180,10 @@ export function ConfigSection({
"config/cameras", "config/cameras",
"views/settings", "views/settings",
"common", "common",
"components/dialog",
]); ]);
const [isOpen, setIsOpen] = useState(!defaultCollapsed); const [isOpen, setIsOpen] = useState(!defaultCollapsed);
const { send: sendRestart } = useRestart();
// Create a key for this section's pending data // Create a key for this section's pending data
const pendingDataKey = useMemo( const pendingDataKey = useMemo(
@ -212,6 +216,7 @@ export function ConfigSection({
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [formKey, setFormKey] = useState(0); const [formKey, setFormKey] = useState(0);
const [isResetDialogOpen, setIsResetDialogOpen] = useState(false); const [isResetDialogOpen, setIsResetDialogOpen] = useState(false);
const [restartDialogOpen, setRestartDialogOpen] = useState(false);
const isResettingRef = useRef(false); const isResettingRef = useRef(false);
const isInitializingRef = useRef(true); const isInitializingRef = useRef(true);
@ -562,14 +567,31 @@ export function ConfigSection({
requires_restart: needsRestart ? 1 : 0, requires_restart: needsRestart ? 1 : 0,
}); });
toast.success( if (needsRestart) {
t(needsRestart ? "toast.successRestartRequired" : "toast.success", { toast.success(
ns: "views/settings", t("toast.successRestartRequired", {
defaultValue: needsRestart ns: "views/settings",
? "Settings saved successfully. Restart Frigate to apply your changes." defaultValue:
: "Settings saved successfully", "Settings saved successfully. Restart Frigate to apply your changes.",
}), }),
); {
action: (
<a onClick={() => setRestartDialogOpen(true)}>
<Button>
{t("restart.button", { ns: "components/dialog" })}
</Button>
</a>
),
},
);
} else {
toast.success(
t("toast.success", {
ns: "views/settings",
defaultValue: "Settings saved successfully",
}),
);
}
setPendingData(null); setPendingData(null);
refreshConfig(); refreshConfig();
@ -933,16 +955,61 @@ export function ConfigSection({
if (collapsible) { if (collapsible) {
return ( return (
<Collapsible open={isOpen} onOpenChange={setIsOpen}> <>
<div className="space-y-3"> <Collapsible open={isOpen} onOpenChange={setIsOpen}>
<CollapsibleTrigger asChild> <div className="space-y-3">
<div className="flex cursor-pointer items-center justify-between"> <CollapsibleTrigger asChild>
<div className="flex cursor-pointer items-center justify-between">
<div className="flex items-center gap-3">
{isOpen ? (
<LuChevronDown className="h-4 w-4 text-muted-foreground" />
) : (
<LuChevronRight className="h-4 w-4 text-muted-foreground" />
)}
<Heading as="h4">{title}</Heading>
{showOverrideIndicator &&
level === "camera" &&
isOverridden && (
<Badge variant="secondary" className="text-xs">
{t("button.overridden", {
ns: "common",
defaultValue: "Overridden",
})}
</Badge>
)}
{hasChanges && (
<Badge variant="outline" className="text-xs">
{t("modified", {
ns: "common",
defaultValue: "Modified",
})}
</Badge>
)}
</div>
</div>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="pl-7">{sectionContent}</div>
</CollapsibleContent>
</div>
</Collapsible>
<RestartDialog
isOpen={restartDialogOpen}
onClose={() => setRestartDialogOpen(false)}
onRestart={() => sendRestart("restart")}
/>
</>
);
}
return (
<>
<div className="space-y-3">
{shouldShowTitle && (
<div className="flex items-start justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{isOpen ? (
<LuChevronDown className="h-4 w-4 text-muted-foreground" />
) : (
<LuChevronRight className="h-4 w-4 text-muted-foreground" />
)}
<Heading as="h4">{title}</Heading> <Heading as="h4">{title}</Heading>
{showOverrideIndicator && {showOverrideIndicator &&
level === "camera" && level === "camera" &&
@ -956,55 +1023,26 @@ export function ConfigSection({
)} )}
{hasChanges && ( {hasChanges && (
<Badge variant="outline" className="text-xs"> <Badge variant="outline" className="text-xs">
{t("modified", { {t("modified", { ns: "common", defaultValue: "Modified" })}
ns: "common",
defaultValue: "Modified",
})}
</Badge> </Badge>
)} )}
</div> </div>
</div> {sectionDescription && (
</CollapsibleTrigger> <p className="text-sm text-muted-foreground">
{sectionDescription}
<CollapsibleContent> </p>
<div className="pl-7">{sectionContent}</div>
</CollapsibleContent>
</div>
</Collapsible>
);
}
return (
<div className="space-y-3">
{shouldShowTitle && (
<div className="flex items-start justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-3">
<Heading as="h4">{title}</Heading>
{showOverrideIndicator && level === "camera" && isOverridden && (
<Badge variant="secondary" className="text-xs">
{t("button.overridden", {
ns: "common",
defaultValue: "Overridden",
})}
</Badge>
)}
{hasChanges && (
<Badge variant="outline" className="text-xs">
{t("modified", { ns: "common", defaultValue: "Modified" })}
</Badge>
)} )}
</div> </div>
{sectionDescription && (
<p className="text-sm text-muted-foreground">
{sectionDescription}
</p>
)}
</div> </div>
</div> )}
)}
{sectionContent} {sectionContent}
</div> </div>
<RestartDialog
isOpen={restartDialogOpen}
onClose={() => setRestartDialogOpen(false)}
onRestart={() => sendRestart("restart")}
/>
</>
); );
} }