mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-31 16:12:19 +03:00
Dynamically enable/disable cameras (#16894)
* config options * metrics * stop and restart ffmpeg processes * dispatcher * frontend websocket * buttons for testing * don't recreate log pipe * add/remove cam from birdseye when enabling/disabling * end all objects and send empty camera activity * enable/disable switch in ui * disable buttons when camera is disabled * use enabled_in_config for some frontend checks * tweaks * handle settings pane with disabled cameras * frontend tweaks * change to debug log * mqtt docs * tweak * ensure all ffmpeg processes are initially started * clean up * use zmq * remove camera metrics * remove camera metrics * tweaks * frontend tweaks
This commit is contained in:
@@ -101,12 +101,14 @@ function Live() {
|
||||
) {
|
||||
const group = config.camera_groups[cameraGroup];
|
||||
return Object.values(config.cameras)
|
||||
.filter((conf) => conf.enabled && group.cameras.includes(conf.name))
|
||||
.filter(
|
||||
(conf) => conf.enabled_in_config && group.cameras.includes(conf.name),
|
||||
)
|
||||
.sort((aConf, bConf) => aConf.ui.order - bConf.ui.order);
|
||||
}
|
||||
|
||||
return Object.values(config.cameras)
|
||||
.filter((conf) => conf.ui.dashboard && conf.enabled)
|
||||
.filter((conf) => conf.ui.dashboard && conf.enabled_in_config)
|
||||
.sort((aConf, bConf) => aConf.ui.order - bConf.ui.order);
|
||||
}, [config, cameraGroup]);
|
||||
|
||||
|
||||
+66
-18
@@ -39,6 +39,7 @@ import SearchSettingsView from "@/views/settings/SearchSettingsView";
|
||||
import UiSettingsView from "@/views/settings/UiSettingsView";
|
||||
import { useSearchEffect } from "@/hooks/use-overlay-state";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { useInitialCameraState } from "@/api/ws";
|
||||
|
||||
const allSettingsViews = [
|
||||
"UI settings",
|
||||
@@ -71,12 +72,33 @@ export default function Settings() {
|
||||
}
|
||||
|
||||
return Object.values(config.cameras)
|
||||
.filter((conf) => conf.ui.dashboard && conf.enabled)
|
||||
.filter((conf) => conf.ui.dashboard && conf.enabled_in_config)
|
||||
.sort((aConf, bConf) => aConf.ui.order - bConf.ui.order);
|
||||
}, [config]);
|
||||
|
||||
const [selectedCamera, setSelectedCamera] = useState<string>("");
|
||||
|
||||
const { payload: allCameraStates } = useInitialCameraState(
|
||||
cameras.length > 0 ? cameras[0].name : "",
|
||||
true,
|
||||
);
|
||||
|
||||
const cameraEnabledStates = useMemo(() => {
|
||||
const states: Record<string, boolean> = {};
|
||||
if (allCameraStates) {
|
||||
Object.entries(allCameraStates).forEach(([camName, state]) => {
|
||||
states[camName] = state.config?.enabled ?? false;
|
||||
});
|
||||
}
|
||||
// fallback to config if ws data isn’t available yet
|
||||
cameras.forEach((cam) => {
|
||||
if (!(cam.name in states)) {
|
||||
states[cam.name] = cam.enabled;
|
||||
}
|
||||
});
|
||||
return states;
|
||||
}, [allCameraStates, cameras]);
|
||||
|
||||
const [filterZoneMask, setFilterZoneMask] = useState<PolygonType[]>();
|
||||
|
||||
const handleDialog = useCallback(
|
||||
@@ -91,10 +113,25 @@ export default function Settings() {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (cameras.length > 0 && selectedCamera === "") {
|
||||
setSelectedCamera(cameras[0].name);
|
||||
if (cameras.length > 0) {
|
||||
if (!selectedCamera) {
|
||||
// Set to first enabled camera initially if no selection
|
||||
const firstEnabledCamera =
|
||||
cameras.find((cam) => cameraEnabledStates[cam.name]) || cameras[0];
|
||||
setSelectedCamera(firstEnabledCamera.name);
|
||||
} else if (
|
||||
!cameraEnabledStates[selectedCamera] &&
|
||||
page !== "camera settings"
|
||||
) {
|
||||
// Switch to first enabled camera if current one is disabled, unless on "camera settings" page
|
||||
const firstEnabledCamera =
|
||||
cameras.find((cam) => cameraEnabledStates[cam.name]) || cameras[0];
|
||||
if (firstEnabledCamera.name !== selectedCamera) {
|
||||
setSelectedCamera(firstEnabledCamera.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [cameras, selectedCamera]);
|
||||
}, [cameras, selectedCamera, cameraEnabledStates, page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tabsRef.current) {
|
||||
@@ -177,6 +214,8 @@ export default function Settings() {
|
||||
allCameras={cameras}
|
||||
selectedCamera={selectedCamera}
|
||||
setSelectedCamera={setSelectedCamera}
|
||||
cameraEnabledStates={cameraEnabledStates}
|
||||
currentPage={page}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -244,17 +283,21 @@ type CameraSelectButtonProps = {
|
||||
allCameras: CameraConfig[];
|
||||
selectedCamera: string;
|
||||
setSelectedCamera: React.Dispatch<React.SetStateAction<string>>;
|
||||
cameraEnabledStates: Record<string, boolean>;
|
||||
currentPage: SettingsType;
|
||||
};
|
||||
|
||||
function CameraSelectButton({
|
||||
allCameras,
|
||||
selectedCamera,
|
||||
setSelectedCamera,
|
||||
cameraEnabledStates,
|
||||
currentPage,
|
||||
}: CameraSelectButtonProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (!allCameras.length) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const trigger = (
|
||||
@@ -283,19 +326,24 @@ function CameraSelectButton({
|
||||
)}
|
||||
<div className="scrollbar-container mb-5 h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden p-4 md:mb-1">
|
||||
<div className="flex flex-col gap-2.5">
|
||||
{allCameras.map((item) => (
|
||||
<FilterSwitch
|
||||
key={item.name}
|
||||
isChecked={item.name === selectedCamera}
|
||||
label={item.name.replaceAll("_", " ")}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setSelectedCamera(item.name);
|
||||
setOpen(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{allCameras.map((item) => {
|
||||
const isEnabled = cameraEnabledStates[item.name];
|
||||
const isCameraSettingsPage = currentPage === "camera settings";
|
||||
return (
|
||||
<FilterSwitch
|
||||
key={item.name}
|
||||
isChecked={item.name === selectedCamera}
|
||||
label={item.name.replaceAll("_", " ")}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked && (isEnabled || isCameraSettingsPage)) {
|
||||
setSelectedCamera(item.name);
|
||||
setOpen(false);
|
||||
}
|
||||
}}
|
||||
disabled={!isEnabled && !isCameraSettingsPage}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user