fix config saving

This commit is contained in:
Josh Hawkins 2026-02-11 13:35:59 -06:00
parent 093a25e9ac
commit 9c43c73a3b
3 changed files with 33 additions and 9 deletions

View File

@ -57,6 +57,7 @@ import { StatusBarMessagesContext } from "@/context/statusbar-provider";
import { import {
cameraUpdateTopicMap, cameraUpdateTopicMap,
buildOverrides, buildOverrides,
buildConfigDataForPath,
sanitizeSectionData as sharedSanitizeSectionData, sanitizeSectionData as sharedSanitizeSectionData,
requiresRestartForOverrides as sharedRequiresRestartForOverrides, requiresRestartForOverrides as sharedRequiresRestartForOverrides,
} from "@/utils/configUtil"; } from "@/utils/configUtil";
@ -507,12 +508,11 @@ export function ConfigSection({
const needsRestart = requiresRestartForOverrides(sanitizedOverrides); const needsRestart = requiresRestartForOverrides(sanitizedOverrides);
const configData = buildConfigDataForPath(basePath, sanitizedOverrides);
await axios.put("config/set", { await axios.put("config/set", {
requires_restart: needsRestart ? 1 : 0, requires_restart: needsRestart ? 1 : 0,
update_topic: updateTopic, update_topic: updateTopic,
config_data: { config_data: configData,
[basePath]: sanitizedOverrides,
},
}); });
// log save to console for debugging // log save to console for debugging
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -625,14 +625,12 @@ export function ConfigSection({
? `cameras.${cameraName}.${sectionPath}` ? `cameras.${cameraName}.${sectionPath}`
: sectionPath; : sectionPath;
const configData = ""; const configData = buildConfigDataForPath(basePath, "");
await axios.put("config/set", { await axios.put("config/set", {
requires_restart: requiresRestart ? 0 : 1, requires_restart: requiresRestart ? 0 : 1,
update_topic: updateTopic, update_topic: updateTopic,
config_data: { config_data: configData,
[basePath]: configData,
},
}); });
// log reset to console for debugging // log reset to console for debugging

View File

@ -83,7 +83,10 @@ import axios from "axios";
import { toast } from "sonner"; import { toast } from "sonner";
import { mutate } from "swr"; import { mutate } from "swr";
import { RJSFSchema } from "@rjsf/utils"; import { RJSFSchema } from "@rjsf/utils";
import { prepareSectionSavePayload } from "@/utils/configUtil"; import {
buildConfigDataForPath,
prepareSectionSavePayload,
} from "@/utils/configUtil";
import ActivityIndicator from "@/components/indicators/activity-indicator"; import ActivityIndicator from "@/components/indicators/activity-indicator";
import RestartDialog from "@/components/overlay/dialog/RestartDialog"; import RestartDialog from "@/components/overlay/dialog/RestartDialog";
import SaveAllPreviewPopover, { import SaveAllPreviewPopover, {
@ -764,10 +767,14 @@ export default function Settings() {
continue; continue;
} }
const configData = buildConfigDataForPath(
payload.basePath,
payload.sanitizedOverrides,
);
await axios.put("config/set", { await axios.put("config/set", {
requires_restart: payload.needsRestart ? 1 : 0, requires_restart: payload.needsRestart ? 1 : 0,
update_topic: payload.updateTopic, update_topic: payload.updateTopic,
config_data: { [payload.basePath]: payload.sanitizedOverrides }, config_data: configData,
}); });
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -9,6 +9,7 @@ import cloneDeep from "lodash/cloneDeep";
import unset from "lodash/unset"; import unset from "lodash/unset";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";
import mergeWith from "lodash/mergeWith"; import mergeWith from "lodash/mergeWith";
import set from "lodash/set";
import { isJsonObject } from "@/lib/utils"; import { isJsonObject } from "@/lib/utils";
import { applySchemaDefaults } from "@/lib/config-schema"; import { applySchemaDefaults } from "@/lib/config-schema";
import { normalizeConfigValue } from "@/hooks/use-config-override"; import { normalizeConfigValue } from "@/hooks/use-config-override";
@ -169,6 +170,24 @@ export function sanitizeSectionData(
return cleaned; 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<string, unknown> {
const configData: Record<string, unknown> = {};
set(configData, path, value);
return configData;
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// requiresRestartForOverrides — determine whether a restart is needed // requiresRestartForOverrides — determine whether a restart is needed
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------