diff --git a/frigate/api/app.py b/frigate/api/app.py index 71b9dbc74..383b76151 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -158,6 +158,18 @@ def config(request: Request): for zone_name, zone in config_obj.cameras[camera_name].zones.items(): camera_dict["zones"][zone_name]["color"] = zone.color + # Re-dump profile overrides with exclude_unset so that only + # explicitly-set fields are returned (not Pydantic defaults). + # Without this, the frontend merges defaults (e.g. threshold=30) + # over the camera's actual base values (e.g. threshold=20). + if camera.profiles: + for profile_name, profile_config in camera.profiles.items(): + camera_dict.setdefault("profiles", {})[profile_name] = ( + profile_config.model_dump( + mode="json", warnings="none", exclude_unset=True + ) + ) + # remove go2rtc stream passwords go2rtc: dict[str, Any] = config_obj.go2rtc.model_dump( mode="json", warnings="none", exclude_none=True @@ -229,9 +241,7 @@ def set_profile(request: Request, body: ProfileSetBody): content={"success": False, "message": err}, status_code=400, ) - request.app.dispatcher.publish( - "profile/state", body.profile or "none", retain=True - ) + request.app.dispatcher.publish("profile/state", body.profile or "none", retain=True) return JSONResponse( content={ "success": True, diff --git a/frigate/config/profile_manager.py b/frigate/config/profile_manager.py index ac07cf54c..d5cd6f921 100644 --- a/frigate/config/profile_manager.py +++ b/frigate/config/profile_manager.py @@ -101,7 +101,9 @@ class ProfileManager: """ if profile_name is not None: if profile_name not in self.config.profiles: - return f"Profile '{profile_name}' is not defined in the profiles section" + return ( + f"Profile '{profile_name}' is not defined in the profiles section" + ) # Track which camera/section pairs get changed for ZMQ publishing changed: dict[str, set[str]] = {} diff --git a/web/src/utils/configUtil.ts b/web/src/utils/configUtil.ts index 0be14059b..1707bc720 100644 --- a/web/src/utils/configUtil.ts +++ b/web/src/utils/configUtil.ts @@ -533,10 +533,21 @@ export function prepareSectionSavePayload(opts: { ? {} : rawSectionValue; + // For profile sections, also hide restart-required fields to match + // effectiveHiddenFields in BaseSection (prevents spurious deletion markers + // for fields that are hidden from the form during profile editing). + let hiddenFieldsForSanitize = sectionConfig.hiddenFields; + if (profileInfo.isProfile && sectionConfig.restartRequired?.length) { + const base = sectionConfig.hiddenFields ?? []; + hiddenFieldsForSanitize = [ + ...new Set([...base, ...sectionConfig.restartRequired]), + ]; + } + // Sanitize raw form data const rawData = sanitizeSectionData( rawFormData as ConfigSectionData, - sectionConfig.hiddenFields, + hiddenFieldsForSanitize, ); // Compute schema defaults