From 9c43c73a3b5dceb1c9cfae8fb1fc5ecb6896cf1f Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:35:59 -0600 Subject: [PATCH] fix config saving --- .../config-form/sections/BaseSection.tsx | 12 +++++------- web/src/pages/Settings.tsx | 11 +++++++++-- web/src/utils/configUtil.ts | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/web/src/components/config-form/sections/BaseSection.tsx b/web/src/components/config-form/sections/BaseSection.tsx index 031e3101f..8ad69f401 100644 --- a/web/src/components/config-form/sections/BaseSection.tsx +++ b/web/src/components/config-form/sections/BaseSection.tsx @@ -57,6 +57,7 @@ import { StatusBarMessagesContext } from "@/context/statusbar-provider"; import { cameraUpdateTopicMap, buildOverrides, + buildConfigDataForPath, sanitizeSectionData as sharedSanitizeSectionData, requiresRestartForOverrides as sharedRequiresRestartForOverrides, } from "@/utils/configUtil"; @@ -507,12 +508,11 @@ export function ConfigSection({ const needsRestart = requiresRestartForOverrides(sanitizedOverrides); + const configData = buildConfigDataForPath(basePath, sanitizedOverrides); await axios.put("config/set", { requires_restart: needsRestart ? 1 : 0, update_topic: updateTopic, - config_data: { - [basePath]: sanitizedOverrides, - }, + config_data: configData, }); // log save to console for debugging // eslint-disable-next-line no-console @@ -625,14 +625,12 @@ export function ConfigSection({ ? `cameras.${cameraName}.${sectionPath}` : sectionPath; - const configData = ""; + const configData = buildConfigDataForPath(basePath, ""); await axios.put("config/set", { requires_restart: requiresRestart ? 0 : 1, update_topic: updateTopic, - config_data: { - [basePath]: configData, - }, + config_data: configData, }); // log reset to console for debugging diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index 8d486a558..95629ef90 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -83,7 +83,10 @@ import axios from "axios"; import { toast } from "sonner"; import { mutate } from "swr"; import { RJSFSchema } from "@rjsf/utils"; -import { prepareSectionSavePayload } from "@/utils/configUtil"; +import { + buildConfigDataForPath, + prepareSectionSavePayload, +} from "@/utils/configUtil"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import RestartDialog from "@/components/overlay/dialog/RestartDialog"; import SaveAllPreviewPopover, { @@ -764,10 +767,14 @@ export default function Settings() { continue; } + const configData = buildConfigDataForPath( + payload.basePath, + payload.sanitizedOverrides, + ); await axios.put("config/set", { requires_restart: payload.needsRestart ? 1 : 0, update_topic: payload.updateTopic, - config_data: { [payload.basePath]: payload.sanitizedOverrides }, + config_data: configData, }); // eslint-disable-next-line no-console diff --git a/web/src/utils/configUtil.ts b/web/src/utils/configUtil.ts index d7cdb85f3..fc47dd1c0 100644 --- a/web/src/utils/configUtil.ts +++ b/web/src/utils/configUtil.ts @@ -9,6 +9,7 @@ import cloneDeep from "lodash/cloneDeep"; import unset from "lodash/unset"; import isEqual from "lodash/isEqual"; import mergeWith from "lodash/mergeWith"; +import set from "lodash/set"; import { isJsonObject } from "@/lib/utils"; import { applySchemaDefaults } from "@/lib/config-schema"; import { normalizeConfigValue } from "@/hooks/use-config-override"; @@ -169,6 +170,24 @@ export function sanitizeSectionData( return cleaned; } +// --------------------------------------------------------------------------- +// buildConfigDataForPath — convert dotted path to nested config_data payload +// --------------------------------------------------------------------------- + +// Converts a dotted path (e.g. "cameras.front_door.detect") and a value into +// a properly nested config_data object (e.g. { cameras: { front_door: { detect: value } } }). +// This ensures the backend's flatten_config_data function can correctly distinguish +// between path separators (dots in the path) and literal dots in keys +// (e.g. "frigate.foo.bar" in logger.logs). +export function buildConfigDataForPath( + path: string, + value: unknown, +): Record { + const configData: Record = {}; + set(configData, path, value); + return configData; +} + // --------------------------------------------------------------------------- // requiresRestartForOverrides — determine whether a restart is needed // ---------------------------------------------------------------------------