diff --git a/web/src/components/config-form/section-configs/audio.ts b/web/src/components/config-form/section-configs/audio.ts index 81ddb9b0a..09fe4e974 100644 --- a/web/src/components/config-form/section-configs/audio.ts +++ b/web/src/components/config-form/section-configs/audio.ts @@ -25,14 +25,7 @@ const audio: SectionConfigOverrides = { }, }, global: { - restartRequired: [ - "enabled", - "listen", - "filters", - "min_volume", - "max_not_heard", - "num_threads", - ], + restartRequired: ["num_threads"], }, camera: { restartRequired: ["num_threads"], diff --git a/web/src/components/config-form/section-configs/detect.ts b/web/src/components/config-form/section-configs/detect.ts index 2c3da7b06..b0073d1d3 100644 --- a/web/src/components/config-form/section-configs/detect.ts +++ b/web/src/components/config-form/section-configs/detect.ts @@ -30,16 +30,7 @@ const detect: SectionConfigOverrides = { ], }, global: { - restartRequired: [ - "enabled", - "width", - "height", - "fps", - "min_initialized", - "max_disappeared", - "annotation_offset", - "stationary", - ], + restartRequired: ["width", "height", "min_initialized", "max_disappeared"], }, camera: { restartRequired: ["width", "height", "min_initialized", "max_disappeared"], diff --git a/web/src/components/config-form/section-configs/motion.ts b/web/src/components/config-form/section-configs/motion.ts index 41b5738d3..2cab58117 100644 --- a/web/src/components/config-form/section-configs/motion.ts +++ b/web/src/components/config-form/section-configs/motion.ts @@ -29,17 +29,7 @@ const motion: SectionConfigOverrides = { ], }, global: { - restartRequired: [ - "enabled", - "threshold", - "lightning_threshold", - "improve_contrast", - "contour_area", - "delta_alpha", - "frame_alpha", - "frame_height", - "mqtt_off_delay", - ], + restartRequired: ["frame_height"], }, camera: { restartRequired: ["frame_height"], diff --git a/web/src/components/config-form/section-configs/objects.ts b/web/src/components/config-form/section-configs/objects.ts index a70746c49..bf5f6c350 100644 --- a/web/src/components/config-form/section-configs/objects.ts +++ b/web/src/components/config-form/section-configs/objects.ts @@ -83,7 +83,7 @@ const objects: SectionConfigOverrides = { }, }, global: { - restartRequired: ["track", "alert", "detect", "filters", "genai"], + restartRequired: [], hiddenFields: [ "enabled_in_config", "mask", diff --git a/web/src/components/config-form/section-configs/record.ts b/web/src/components/config-form/section-configs/record.ts index c47d67ad0..53803eed9 100644 --- a/web/src/components/config-form/section-configs/record.ts +++ b/web/src/components/config-form/section-configs/record.ts @@ -29,16 +29,7 @@ const record: SectionConfigOverrides = { }, }, global: { - restartRequired: [ - "enabled", - "expire_interval", - "continuous", - "motion", - "alerts", - "detections", - "preview", - "export", - ], + restartRequired: [], }, camera: { restartRequired: [], diff --git a/web/src/components/config-form/section-configs/review.ts b/web/src/components/config-form/section-configs/review.ts index bb3ec4ca4..7d7a84756 100644 --- a/web/src/components/config-form/section-configs/review.ts +++ b/web/src/components/config-form/section-configs/review.ts @@ -44,7 +44,7 @@ const review: SectionConfigOverrides = { }, }, global: { - restartRequired: ["alerts", "detections", "genai"], + restartRequired: [], }, camera: { restartRequired: [], diff --git a/web/src/components/config-form/section-configs/snapshots.ts b/web/src/components/config-form/section-configs/snapshots.ts index b098d84a5..8f08fa843 100644 --- a/web/src/components/config-form/section-configs/snapshots.ts +++ b/web/src/components/config-form/section-configs/snapshots.ts @@ -27,14 +27,7 @@ const snapshots: SectionConfigOverrides = { }, }, global: { - restartRequired: [ - "enabled", - "bounding_box", - "crop", - "quality", - "timestamp", - "retain", - ], + restartRequired: [], hiddenFields: ["enabled_in_config", "required_zones"], }, camera: { diff --git a/web/src/components/config-form/sections/BaseSection.tsx b/web/src/components/config-form/sections/BaseSection.tsx index 821857ae6..6340691ce 100644 --- a/web/src/components/config-form/sections/BaseSection.tsx +++ b/web/src/components/config-form/sections/BaseSection.tsx @@ -56,6 +56,7 @@ import ActivityIndicator from "@/components/indicators/activity-indicator"; import { StatusBarMessagesContext } from "@/context/statusbar-provider"; import { cameraUpdateTopicMap, + globalCameraDefaultSections, buildOverrides, buildConfigDataForPath, sanitizeSectionData as sharedSanitizeSectionData, @@ -234,7 +235,10 @@ export function ConfigSection({ ? cameraUpdateTopicMap[sectionPath] ? `config/cameras/${cameraName}/${cameraUpdateTopicMap[sectionPath]}` : undefined - : `config/${sectionPath}`; + : globalCameraDefaultSections.has(sectionPath) && + cameraUpdateTopicMap[sectionPath] + ? `config/cameras/*/${cameraUpdateTopicMap[sectionPath]}` + : `config/${sectionPath}`; // Default: show title for camera level (since it might be collapsible), hide for global const shouldShowTitle = showTitle ?? effectiveLevel === "camera"; diff --git a/web/src/utils/configUtil.ts b/web/src/utils/configUtil.ts index 9d2327cb3..77258d64a 100644 --- a/web/src/utils/configUtil.ts +++ b/web/src/utils/configUtil.ts @@ -54,6 +54,19 @@ export const cameraUpdateTopicMap: Record = { ui: "ui", }; +// Sections where global config serves as the default for per-camera config. +// Global updates to these sections are fanned out to all cameras via wildcard. +export const globalCameraDefaultSections = new Set([ + "detect", + "objects", + "motion", + "record", + "snapshots", + "review", + "audio", + "notifications", +]); + // --------------------------------------------------------------------------- // buildOverrides — pure recursive diff of current vs stored config & defaults // --------------------------------------------------------------------------- @@ -476,6 +489,9 @@ export function prepareSectionSavePayload(opts: { if (level === "camera" && cameraName) { const topic = cameraUpdateTopicMap[sectionPath]; updateTopic = topic ? `config/cameras/${cameraName}/${topic}` : undefined; + } else if (globalCameraDefaultSections.has(sectionPath)) { + const topic = cameraUpdateTopicMap[sectionPath]; + updateTopic = topic ? `config/cameras/*/${topic}` : `config/${sectionPath}`; } else { updateTopic = `config/${sectionPath}`; }