require restart when enabling camera that is disabled in config

This commit is contained in:
Josh Hawkins 2026-02-14 08:02:47 -06:00
parent c62e0d3f46
commit 65dc9b2bac
2 changed files with 37 additions and 9 deletions

View File

@ -430,7 +430,7 @@
"enableDesc": "Temporarily disable an enabled camera until Frigate restarts. Disabling a camera completely stops Frigate's processing of this camera's streams. Detection, recording, and debugging will be unavailable.<br /> <em>Note: This does not disable go2rtc restreams.</em>", "enableDesc": "Temporarily disable an enabled camera until Frigate restarts. Disabling a camera completely stops Frigate's processing of this camera's streams. Detection, recording, and debugging will be unavailable.<br /> <em>Note: This does not disable go2rtc restreams.</em>",
"disableLabel": "Disabled cameras", "disableLabel": "Disabled cameras",
"disableDesc": "Enable a camera that is currently not visible in the UI and disabled in the configuration. A restart of Frigate is required after enabling.", "disableDesc": "Enable a camera that is currently not visible in the UI and disabled in the configuration. A restart of Frigate is required after enabling.",
"enableSuccess": "Enabled {{cameraName}} in configuration" "enableSuccess": "Enabled {{cameraName}} in configuration. Restart Frigate to apply the changes."
}, },
"cameraConfig": { "cameraConfig": {
"add": "Add Camera", "add": "Add Camera",

View File

@ -19,10 +19,12 @@ import { isDesktop } from "react-device-detect";
import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel"; import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Trans } from "react-i18next"; import { Trans } from "react-i18next";
import { useEnabledState } from "@/api/ws"; import { useEnabledState, useRestart } from "@/api/ws";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import axios from "axios"; import axios from "axios";
import ActivityIndicator from "@/components/indicators/activity-indicator"; import ActivityIndicator from "@/components/indicators/activity-indicator";
import RestartDialog from "@/components/overlay/dialog/RestartDialog";
import RestartRequiredIndicator from "@/components/indicators/RestartRequiredIndicator";
type CameraManagementViewProps = { type CameraManagementViewProps = {
setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>; setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>;
@ -44,6 +46,10 @@ export default function CameraManagementView({
); // Track camera being edited ); // Track camera being edited
const [showWizard, setShowWizard] = useState(false); const [showWizard, setShowWizard] = useState(false);
// State for restart dialog when enabling a disabled camera
const [restartDialogOpen, setRestartDialogOpen] = useState(false);
const { send: sendRestart } = useRestart();
// List of cameras for dropdown // List of cameras for dropdown
const enabledCameras = useMemo(() => { const enabledCameras = useMemo(() => {
if (config) { if (config) {
@ -148,6 +154,7 @@ export default function CameraManagementView({
htmlFor={"disabled-cameras-switch"} htmlFor={"disabled-cameras-switch"}
> >
{t("cameraManagement.streams.disableLabel")} {t("cameraManagement.streams.disableLabel")}
<RestartRequiredIndicator className="ml-1" />
</Label> </Label>
<p className="hidden text-sm text-muted-foreground md:block"> <p className="hidden text-sm text-muted-foreground md:block">
{t("cameraManagement.streams.disableDesc")} {t("cameraManagement.streams.disableDesc")}
@ -166,6 +173,7 @@ export default function CameraManagementView({
<CameraConfigEnableSwitch <CameraConfigEnableSwitch
cameraName={camera} cameraName={camera}
onConfigChanged={updateConfig} onConfigChanged={updateConfig}
setRestartDialogOpen={setRestartDialogOpen}
/> />
</div> </div>
))} ))}
@ -213,6 +221,11 @@ export default function CameraManagementView({
open={showWizard} open={showWizard}
onClose={() => setShowWizard(false)} onClose={() => setShowWizard(false)}
/> />
<RestartDialog
isOpen={restartDialogOpen}
onClose={() => setRestartDialogOpen(false)}
onRestart={() => sendRestart("restart")}
/>
</> </>
); );
} }
@ -238,13 +251,22 @@ function CameraEnableSwitch({ cameraName }: CameraEnableSwitchProps) {
); );
} }
type CameraConfigEnableSwitchProps = {
cameraName: string;
setRestartDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
onConfigChanged: () => Promise<unknown>;
};
function CameraConfigEnableSwitch({ function CameraConfigEnableSwitch({
cameraName, cameraName,
onConfigChanged, onConfigChanged,
}: CameraEnableSwitchProps & { setRestartDialogOpen,
onConfigChanged: () => Promise<unknown>; }: CameraConfigEnableSwitchProps) {
}) { const { t } = useTranslation([
const { t } = useTranslation(["common", "views/settings"]); "common",
"views/settings",
"components/dialog",
]);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const onCheckedChange = useCallback( const onCheckedChange = useCallback(
@ -257,7 +279,7 @@ function CameraConfigEnableSwitch({
try { try {
await axios.put("config/set", { await axios.put("config/set", {
requires_restart: 0, requires_restart: 1,
config_data: { config_data: {
cameras: { cameras: {
[cameraName]: { [cameraName]: {
@ -265,7 +287,6 @@ function CameraConfigEnableSwitch({
}, },
}, },
}, },
update_topic: `config/cameras/${cameraName}/enabled`,
}); });
await onConfigChanged(); await onConfigChanged();
@ -277,6 +298,13 @@ function CameraConfigEnableSwitch({
}), }),
{ {
position: "top-center", position: "top-center",
action: (
<a onClick={() => setRestartDialogOpen(true)}>
<Button>
{t("restart.button", { ns: "components/dialog" })}
</Button>
</a>
),
}, },
); );
} catch (error) { } catch (error) {
@ -296,7 +324,7 @@ function CameraConfigEnableSwitch({
setIsSaving(false); setIsSaving(false);
} }
}, },
[cameraName, isSaving, onConfigChanged, t], [cameraName, isSaving, onConfigChanged, setRestartDialogOpen, t],
); );
return ( return (