Add dynamic configuration for more fields (#22295)

* face recognition dynamic config

* lpr dynamic config

* safe changes for birdseye dynamic config

* bird classification dynamic config

* always assign new config to stats emitter to make telemetry fields dynamic

* add wildcard support for camera config updates in config_set

* update restart required fields for global sections

* add test

* fix rebase issue

* collapsible settings sidebar

use the preexisting control available with shadcn's sidebar (cmd/ctrl-B) to give users more space to set masks/zones on smaller screens

* dynamic ffmpeg

* ensure previews dir exists

when ffmpeg processes restart, there's a brief window where the preview frame generation pipeline is torn down and restarted. before these changes, ffmpeg only restarted on crash/stall recovery or full Frigate restart. Now that ffmpeg restarts happen on-demand via config changes, there's a higher chance a frontend request hits the preview_mp4 or preview_gif endpoints during that brief restart window when the directory might not exist yet. The existing os.listdir() call would throw FileNotFoundError without a directory existence check. this fix just checks if the directory exists and returns 404 if not, exactly how preview_thumbnail already handles the same scenario a few lines below

* global ffmpeg section

* clean up

* tweak

* fix test
This commit is contained in:
Josh Hawkins
2026-03-06 14:45:39 -06:00
committed by GitHub
parent c9bd907721
commit c2e667c0dd
29 changed files with 489 additions and 146 deletions

View File

@@ -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"],

View File

@@ -28,10 +28,7 @@ const birdseye: SectionConfigOverrides = {
"width",
"height",
"quality",
"mode",
"layout.scaling_factor",
"inactivity_threshold",
"layout.max_cameras",
"idle_heartbeat_fps",
],
uiSchema: {

View File

@@ -3,7 +3,7 @@ import type { SectionConfigOverrides } from "./types";
const classification: SectionConfigOverrides = {
base: {
sectionDocs: "/configuration/custom_classification/object_classification",
restartRequired: ["bird.enabled", "bird.threshold"],
restartRequired: ["bird.enabled"],
hiddenFields: ["custom"],
advancedFields: [],
},

View File

@@ -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"],

View File

@@ -32,18 +32,7 @@ const faceRecognition: SectionConfigOverrides = {
"blur_confidence_filter",
"device",
],
restartRequired: [
"enabled",
"model_size",
"unknown_score",
"detection_threshold",
"recognition_threshold",
"min_area",
"min_faces",
"save_attempts",
"blur_confidence_filter",
"device",
],
restartRequired: ["enabled", "model_size", "device"],
},
};

View File

@@ -116,16 +116,7 @@ const ffmpeg: SectionConfigOverrides = {
},
},
global: {
restartRequired: [
"path",
"global_args",
"hwaccel_args",
"input_args",
"output_args",
"retry_interval",
"apple_compatibility",
"gpu",
],
restartRequired: [],
fieldOrder: [
"hwaccel_args",
"path",
@@ -162,17 +153,7 @@ const ffmpeg: SectionConfigOverrides = {
fieldGroups: {
cameraFfmpeg: ["input_args", "hwaccel_args", "output_args"],
},
restartRequired: [
"inputs",
"path",
"global_args",
"hwaccel_args",
"input_args",
"output_args",
"retry_interval",
"apple_compatibility",
"gpu",
],
restartRequired: [],
},
};

View File

@@ -40,21 +40,7 @@ const lpr: SectionConfigOverrides = {
"device",
"replace_rules",
],
restartRequired: [
"enabled",
"model_size",
"detection_threshold",
"min_area",
"recognition_threshold",
"min_plate_length",
"format",
"match_distance",
"known_plates",
"enhancement",
"debug_save_plates",
"device",
"replace_rules",
],
restartRequired: ["model_size", "enhancement", "device"],
uiSchema: {
format: {
"ui:options": { size: "md" },

View File

@@ -31,18 +31,7 @@ const motion: SectionConfigOverrides = {
],
},
global: {
restartRequired: [
"enabled",
"threshold",
"lightning_threshold",
"skip_motion_threshold",
"improve_contrast",
"contour_area",
"delta_alpha",
"frame_alpha",
"frame_height",
"mqtt_off_delay",
],
restartRequired: ["frame_height"],
},
camera: {
restartRequired: ["frame_height"],

View File

@@ -83,7 +83,7 @@ const objects: SectionConfigOverrides = {
},
},
global: {
restartRequired: ["track", "alert", "detect", "filters", "genai"],
restartRequired: [],
hiddenFields: [
"enabled_in_config",
"mask",

View File

@@ -29,16 +29,7 @@ const record: SectionConfigOverrides = {
},
},
global: {
restartRequired: [
"enabled",
"expire_interval",
"continuous",
"motion",
"alerts",
"detections",
"preview",
"export",
],
restartRequired: [],
},
camera: {
restartRequired: [],

View File

@@ -44,7 +44,7 @@ const review: SectionConfigOverrides = {
},
},
global: {
restartRequired: ["alerts", "detections", "genai"],
restartRequired: [],
},
camera: {
restartRequired: [],

View File

@@ -27,14 +27,7 @@ const snapshots: SectionConfigOverrides = {
},
},
global: {
restartRequired: [
"enabled",
"bounding_box",
"crop",
"quality",
"timestamp",
"retain",
],
restartRequired: [],
hiddenFields: ["enabled_in_config", "required_zones"],
},
camera: {

View File

@@ -3,14 +3,7 @@ import type { SectionConfigOverrides } from "./types";
const telemetry: SectionConfigOverrides = {
base: {
sectionDocs: "/configuration/reference",
restartRequired: [
"network_interfaces",
"stats.amd_gpu_stats",
"stats.intel_gpu_stats",
"stats.intel_gpu_device",
"stats.network_bandwidth",
"version_check",
],
restartRequired: ["version_check"],
fieldOrder: ["network_interfaces", "stats", "version_check"],
advancedFields: [],
},

View File

@@ -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";
@@ -827,7 +831,7 @@ export function ConfigSection({
<div
className={cn(
"w-full border-t border-secondary bg-background pb-5 pt-0",
"w-full border-t border-secondary bg-background pt-0",
!noStickyButtons && "sticky bottom-0 z-50",
)}
>