mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-28 19:18:22 +03:00
refactor sections and overrides
This commit is contained in:
parent
43c7193a3a
commit
e09928a7f0
@ -1069,15 +1069,15 @@
|
|||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"label": "Transcription language",
|
"label": "Transcription language",
|
||||||
"description": "Language code used for transcription/translation (for example 'en' for English)."
|
"description": "Language code used for transcription/translation (for example 'en' for English). See https://whisper-api.com/docs/languages/ for supported language codes."
|
||||||
},
|
},
|
||||||
"device": {
|
"device": {
|
||||||
"label": "Transcription device",
|
"label": "Transcription device",
|
||||||
"description": "Device key (CPU/GPU) to run the transcription model on."
|
"description": "Device key (CPU/GPU) to run the transcription model on. Only NVIDIA CUDA GPUs are currently supported for transcription."
|
||||||
},
|
},
|
||||||
"model_size": {
|
"model_size": {
|
||||||
"label": "Model size",
|
"label": "Model size",
|
||||||
"description": "Model size to use for transcription; the small model runs on CPU, large model requires a GPU."
|
"description": "Model size to use for offline audio event transcription."
|
||||||
},
|
},
|
||||||
"live_enabled": {
|
"live_enabled": {
|
||||||
"label": "Live transcription",
|
"label": "Live transcription",
|
||||||
|
|||||||
@ -18,7 +18,9 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"ui": "UI",
|
"ui": "UI",
|
||||||
"globalConfig": "Global Config",
|
"globalConfig": "Global Config",
|
||||||
|
"mqtt": "MQTT",
|
||||||
"cameraConfig": "Camera Config",
|
"cameraConfig": "Camera Config",
|
||||||
|
"cameraMqtt": "Camera MQTT",
|
||||||
"enrichments": "Enrichments",
|
"enrichments": "Enrichments",
|
||||||
"cameraManagement": "Management",
|
"cameraManagement": "Management",
|
||||||
"cameraReview": "Review",
|
"cameraReview": "Review",
|
||||||
|
|||||||
837
web/src/components/config-form/sectionConfigs.ts
Normal file
837
web/src/components/config-form/sectionConfigs.ts
Normal file
@ -0,0 +1,837 @@
|
|||||||
|
/*
|
||||||
|
sectionConfigs.ts — section configuration overrides
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- Centralize UI configuration hints for each config section (field ordering,
|
||||||
|
grouping, hidden/advanced fields, uiSchema overrides, and overrideFields).
|
||||||
|
|
||||||
|
Shape:
|
||||||
|
- Each section key maps to an object with optional `base`, `global`, and
|
||||||
|
`camera` entries where each is a `SectionConfig` (or partial):
|
||||||
|
{
|
||||||
|
base?: SectionConfig; // common defaults (typically camera-level)
|
||||||
|
global?: Partial<SectionConfig>; // overrides for global-level UI
|
||||||
|
camera?: Partial<SectionConfig>; // overrides for camera-level UI
|
||||||
|
}
|
||||||
|
|
||||||
|
Merge rules (used by getSectionConfig):
|
||||||
|
- `base` is the canonical default and is merged with level-specific overrides.
|
||||||
|
- Arrays (e.g., `fieldOrder`, `advancedFields`, etc.) in overrides **replace**
|
||||||
|
the `base` arrays (they are not concatenated).
|
||||||
|
- `uiSchema` in an override **replaces** the base `uiSchema` rather than deep-merging
|
||||||
|
(this keeps widget overrides explicit per level).
|
||||||
|
- Other object properties are deep-merged using lodash.mergeWith with custom
|
||||||
|
behavior for arrays and `uiSchema` as described.
|
||||||
|
|
||||||
|
Example — `ffmpeg`:
|
||||||
|
- `base` (camera defaults) may include `inputs` and a `fieldOrder` that shows
|
||||||
|
`"inputs"` first.
|
||||||
|
- `global` override can replace `fieldOrder` with a different ordering
|
||||||
|
(e.g., omit `inputs` and show `path` first). Calling
|
||||||
|
`getSectionConfig("ffmpeg", "global")` will return the merged config
|
||||||
|
where `fieldOrder` comes from `global` (not concatenated with `base`).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import mergeWith from "lodash/mergeWith";
|
||||||
|
import type { SectionConfig } from "./sections/BaseSection";
|
||||||
|
|
||||||
|
export type SectionConfigOverrides = {
|
||||||
|
base?: SectionConfig;
|
||||||
|
global?: Partial<SectionConfig>;
|
||||||
|
camera?: Partial<SectionConfig>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sectionConfigs: Record<string, SectionConfigOverrides> = {
|
||||||
|
detect: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"fps",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"min_initialized",
|
||||||
|
"max_disappeared",
|
||||||
|
"annotation_offset",
|
||||||
|
"stationary",
|
||||||
|
],
|
||||||
|
fieldGroups: {
|
||||||
|
resolution: ["enabled", "width", "height"],
|
||||||
|
tracking: ["min_initialized", "max_disappeared"],
|
||||||
|
},
|
||||||
|
hiddenFields: ["enabled_in_config"],
|
||||||
|
advancedFields: [
|
||||||
|
"min_initialized",
|
||||||
|
"max_disappeared",
|
||||||
|
"annotation_offset",
|
||||||
|
"stationary",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
record: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"expire_interval",
|
||||||
|
"continuous",
|
||||||
|
"motion",
|
||||||
|
"alerts",
|
||||||
|
"detections",
|
||||||
|
"preview",
|
||||||
|
"export",
|
||||||
|
],
|
||||||
|
fieldGroups: {
|
||||||
|
retention: ["enabled", "continuous", "motion"],
|
||||||
|
events: ["alerts", "detections"],
|
||||||
|
},
|
||||||
|
hiddenFields: ["enabled_in_config", "sync_recordings"],
|
||||||
|
advancedFields: ["expire_interval", "preview", "export"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
snapshots: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"bounding_box",
|
||||||
|
"crop",
|
||||||
|
"quality",
|
||||||
|
"timestamp",
|
||||||
|
"retain",
|
||||||
|
],
|
||||||
|
fieldGroups: {
|
||||||
|
display: ["enabled", "bounding_box", "crop", "quality", "timestamp"],
|
||||||
|
},
|
||||||
|
hiddenFields: ["enabled_in_config"],
|
||||||
|
advancedFields: ["quality", "retain"],
|
||||||
|
uiSchema: {
|
||||||
|
required_zones: {
|
||||||
|
"ui:widget": "zoneNames",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
motion: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"threshold",
|
||||||
|
"lightning_threshold",
|
||||||
|
"improve_contrast",
|
||||||
|
"contour_area",
|
||||||
|
"delta_alpha",
|
||||||
|
"frame_alpha",
|
||||||
|
"frame_height",
|
||||||
|
"mask",
|
||||||
|
"mqtt_off_delay",
|
||||||
|
],
|
||||||
|
fieldGroups: {
|
||||||
|
sensitivity: ["enabled", "threshold", "contour_area"],
|
||||||
|
algorithm: ["improve_contrast", "delta_alpha", "frame_alpha"],
|
||||||
|
},
|
||||||
|
hiddenFields: ["enabled_in_config", "mask", "raw_mask"],
|
||||||
|
advancedFields: [
|
||||||
|
"lightning_threshold",
|
||||||
|
"delta_alpha",
|
||||||
|
"frame_alpha",
|
||||||
|
"frame_height",
|
||||||
|
"mqtt_off_delay",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
objects: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["track", "alert", "detect", "filters"],
|
||||||
|
fieldGroups: {
|
||||||
|
tracking: ["track", "alert", "detect"],
|
||||||
|
filtering: ["filters"],
|
||||||
|
},
|
||||||
|
hiddenFields: [
|
||||||
|
"enabled_in_config",
|
||||||
|
"mask",
|
||||||
|
"raw_mask",
|
||||||
|
"genai.enabled_in_config",
|
||||||
|
"filters.*.mask",
|
||||||
|
"filters.*.raw_mask",
|
||||||
|
],
|
||||||
|
advancedFields: ["filters"],
|
||||||
|
uiSchema: {
|
||||||
|
"filters.*.min_area": {
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"filters.*.max_area": {
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
track: {
|
||||||
|
"ui:widget": "objectLabels",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
genai: {
|
||||||
|
objects: {
|
||||||
|
"ui:widget": "objectLabels",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required_zones: {
|
||||||
|
"ui:widget": "zoneNames",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
enabled_in_config: {
|
||||||
|
"ui:widget": "hidden",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
review: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["alerts", "detections", "genai"],
|
||||||
|
fieldGroups: {},
|
||||||
|
hiddenFields: [
|
||||||
|
"enabled_in_config",
|
||||||
|
"alerts.labels",
|
||||||
|
"alerts.enabled_in_config",
|
||||||
|
"alerts.required_zones",
|
||||||
|
"detections.labels",
|
||||||
|
"detections.enabled_in_config",
|
||||||
|
"detections.required_zones",
|
||||||
|
"genai.enabled_in_config",
|
||||||
|
],
|
||||||
|
advancedFields: [],
|
||||||
|
uiSchema: {
|
||||||
|
genai: {
|
||||||
|
additional_concerns: {
|
||||||
|
"ui:widget": "textarea",
|
||||||
|
},
|
||||||
|
activity_context_prompt: {
|
||||||
|
"ui:widget": "textarea",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"listen",
|
||||||
|
"filters",
|
||||||
|
"min_volume",
|
||||||
|
"max_not_heard",
|
||||||
|
"num_threads",
|
||||||
|
],
|
||||||
|
fieldGroups: {
|
||||||
|
detection: ["enabled", "listen", "filters"],
|
||||||
|
sensitivity: ["min_volume", "max_not_heard"],
|
||||||
|
},
|
||||||
|
hiddenFields: ["enabled_in_config"],
|
||||||
|
advancedFields: ["min_volume", "max_not_heard", "num_threads"],
|
||||||
|
uiSchema: {
|
||||||
|
listen: {
|
||||||
|
"ui:widget": "audioLabels",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
live: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["stream_name", "height", "quality"],
|
||||||
|
fieldGroups: {},
|
||||||
|
hiddenFields: ["enabled_in_config"],
|
||||||
|
advancedFields: ["quality"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timestamp_style: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["position", "format", "color", "thickness"],
|
||||||
|
hiddenFields: ["effect", "enabled_in_config"],
|
||||||
|
advancedFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["enabled", "email"],
|
||||||
|
fieldGroups: {},
|
||||||
|
hiddenFields: ["enabled_in_config"],
|
||||||
|
advancedFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onvif: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"host",
|
||||||
|
"port",
|
||||||
|
"user",
|
||||||
|
"password",
|
||||||
|
"tls_insecure",
|
||||||
|
"ignore_time_mismatch",
|
||||||
|
"autotracking",
|
||||||
|
],
|
||||||
|
hiddenFields: [
|
||||||
|
"autotracking.enabled_in_config",
|
||||||
|
"autotracking.movement_weights",
|
||||||
|
],
|
||||||
|
advancedFields: ["tls_insecure", "ignore_time_mismatch"],
|
||||||
|
overrideFields: [],
|
||||||
|
uiSchema: {
|
||||||
|
autotracking: {
|
||||||
|
required_zones: {
|
||||||
|
"ui:widget": "zoneNames",
|
||||||
|
},
|
||||||
|
track: {
|
||||||
|
"ui:widget": "objectLabels",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ffmpeg: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"inputs",
|
||||||
|
"path",
|
||||||
|
"global_args",
|
||||||
|
"hwaccel_args",
|
||||||
|
"input_args",
|
||||||
|
"output_args",
|
||||||
|
"retry_interval",
|
||||||
|
"apple_compatibility",
|
||||||
|
"gpu",
|
||||||
|
],
|
||||||
|
hiddenFields: [],
|
||||||
|
advancedFields: [
|
||||||
|
"global_args",
|
||||||
|
"hwaccel_args",
|
||||||
|
"input_args",
|
||||||
|
"output_args",
|
||||||
|
"retry_interval",
|
||||||
|
"apple_compatibility",
|
||||||
|
"gpu",
|
||||||
|
],
|
||||||
|
overrideFields: [
|
||||||
|
"path",
|
||||||
|
"global_args",
|
||||||
|
"hwaccel_args",
|
||||||
|
"input_args",
|
||||||
|
"output_args",
|
||||||
|
"retry_interval",
|
||||||
|
"apple_compatibility",
|
||||||
|
"gpu",
|
||||||
|
],
|
||||||
|
uiSchema: {
|
||||||
|
global_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hwaccel_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
input_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
detect: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
record: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
detect: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
record: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
items: {
|
||||||
|
global_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hwaccel_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
input_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
detect: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
record: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
fieldOrder: [
|
||||||
|
"path",
|
||||||
|
"global_args",
|
||||||
|
"hwaccel_args",
|
||||||
|
"input_args",
|
||||||
|
"output_args",
|
||||||
|
"retry_interval",
|
||||||
|
"apple_compatibility",
|
||||||
|
"gpu",
|
||||||
|
],
|
||||||
|
advancedFields: [
|
||||||
|
"global_args",
|
||||||
|
"hwaccel_args",
|
||||||
|
"input_args",
|
||||||
|
"output_args",
|
||||||
|
"retry_interval",
|
||||||
|
"apple_compatibility",
|
||||||
|
"gpu",
|
||||||
|
],
|
||||||
|
uiSchema: {
|
||||||
|
global_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hwaccel_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
input_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output_args: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
detect: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
record: {
|
||||||
|
"ui:widget": "ArrayAsTextWidget",
|
||||||
|
"ui:options": {
|
||||||
|
suppressMultiSchema: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
audio_transcription: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"language",
|
||||||
|
"device",
|
||||||
|
"model_size",
|
||||||
|
"live_enabled",
|
||||||
|
],
|
||||||
|
hiddenFields: ["enabled_in_config"],
|
||||||
|
advancedFields: ["language", "device", "model_size"],
|
||||||
|
overrideFields: ["enabled", "live_enabled"],
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"language",
|
||||||
|
"device",
|
||||||
|
"model_size",
|
||||||
|
"live_enabled",
|
||||||
|
],
|
||||||
|
advancedFields: ["language", "device", "model_size"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
birdseye: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["enabled", "mode", "order"],
|
||||||
|
hiddenFields: [],
|
||||||
|
advancedFields: [],
|
||||||
|
overrideFields: ["enabled", "mode"],
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"restream",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"quality",
|
||||||
|
"mode",
|
||||||
|
"layout",
|
||||||
|
"inactivity_threshold",
|
||||||
|
"idle_heartbeat_fps",
|
||||||
|
],
|
||||||
|
advancedFields: ["width", "height", "quality", "inactivity_threshold"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
face_recognition: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["enabled", "min_area"],
|
||||||
|
hiddenFields: [],
|
||||||
|
advancedFields: ["min_area"],
|
||||||
|
overrideFields: ["enabled", "min_area"],
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"model_size",
|
||||||
|
"unknown_score",
|
||||||
|
"detection_threshold",
|
||||||
|
"recognition_threshold",
|
||||||
|
"min_area",
|
||||||
|
"min_faces",
|
||||||
|
"save_attempts",
|
||||||
|
"blur_confidence_filter",
|
||||||
|
"device",
|
||||||
|
],
|
||||||
|
advancedFields: [
|
||||||
|
"unknown_score",
|
||||||
|
"detection_threshold",
|
||||||
|
"recognition_threshold",
|
||||||
|
"min_area",
|
||||||
|
"min_faces",
|
||||||
|
"save_attempts",
|
||||||
|
"blur_confidence_filter",
|
||||||
|
"device",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lpr: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["enabled", "expire_time", "min_area", "enhancement"],
|
||||||
|
hiddenFields: [],
|
||||||
|
advancedFields: ["expire_time", "min_area", "enhancement"],
|
||||||
|
overrideFields: ["enabled", "min_area", "enhancement"],
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"model_size",
|
||||||
|
"detection_threshold",
|
||||||
|
"min_area",
|
||||||
|
"recognition_threshold",
|
||||||
|
"min_plate_length",
|
||||||
|
"format",
|
||||||
|
"match_distance",
|
||||||
|
"known_plates",
|
||||||
|
"enhancement",
|
||||||
|
"debug_save_plates",
|
||||||
|
"device",
|
||||||
|
"replace_rules",
|
||||||
|
],
|
||||||
|
advancedFields: [
|
||||||
|
"detection_threshold",
|
||||||
|
"recognition_threshold",
|
||||||
|
"min_plate_length",
|
||||||
|
"format",
|
||||||
|
"match_distance",
|
||||||
|
"known_plates",
|
||||||
|
"enhancement",
|
||||||
|
"debug_save_plates",
|
||||||
|
"device",
|
||||||
|
"replace_rules",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
semantic_search: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["triggers"],
|
||||||
|
hiddenFields: [],
|
||||||
|
advancedFields: [],
|
||||||
|
overrideFields: [],
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
fieldOrder: ["enabled", "reindex", "model", "model_size", "device"],
|
||||||
|
advancedFields: ["reindex", "device"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mqtt: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"timestamp",
|
||||||
|
"bounding_box",
|
||||||
|
"crop",
|
||||||
|
"height",
|
||||||
|
"required_zones",
|
||||||
|
"quality",
|
||||||
|
],
|
||||||
|
hiddenFields: [],
|
||||||
|
advancedFields: ["height", "quality"],
|
||||||
|
overrideFields: [],
|
||||||
|
uiSchema: {
|
||||||
|
required_zones: {
|
||||||
|
"ui:widget": "zoneNames",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"host",
|
||||||
|
"port",
|
||||||
|
"user",
|
||||||
|
"password",
|
||||||
|
"topic_prefix",
|
||||||
|
"client_id",
|
||||||
|
"stats_interval",
|
||||||
|
"qos",
|
||||||
|
"tls_ca_certs",
|
||||||
|
"tls_client_cert",
|
||||||
|
"tls_client_key",
|
||||||
|
"tls_insecure",
|
||||||
|
],
|
||||||
|
advancedFields: [
|
||||||
|
"stats_interval",
|
||||||
|
"qos",
|
||||||
|
"tls_ca_certs",
|
||||||
|
"tls_client_cert",
|
||||||
|
"tls_client_key",
|
||||||
|
"tls_insecure",
|
||||||
|
],
|
||||||
|
liveValidate: true,
|
||||||
|
uiSchema: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["dashboard", "order"],
|
||||||
|
hiddenFields: [],
|
||||||
|
advancedFields: [],
|
||||||
|
overrideFields: [],
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
fieldOrder: [
|
||||||
|
"timezone",
|
||||||
|
"time_format",
|
||||||
|
"date_style",
|
||||||
|
"time_style",
|
||||||
|
"unit_system",
|
||||||
|
],
|
||||||
|
advancedFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
database: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["path"],
|
||||||
|
advancedFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"enabled",
|
||||||
|
"reset_admin_password",
|
||||||
|
"cookie_name",
|
||||||
|
"cookie_secure",
|
||||||
|
"session_length",
|
||||||
|
"refresh_time",
|
||||||
|
"native_oauth_url",
|
||||||
|
"failed_login_rate_limit",
|
||||||
|
"trusted_proxies",
|
||||||
|
"hash_iterations",
|
||||||
|
"roles",
|
||||||
|
],
|
||||||
|
hiddenFields: ["admin_first_time_login"],
|
||||||
|
advancedFields: [
|
||||||
|
"cookie_name",
|
||||||
|
"cookie_secure",
|
||||||
|
"session_length",
|
||||||
|
"refresh_time",
|
||||||
|
"failed_login_rate_limit",
|
||||||
|
"trusted_proxies",
|
||||||
|
"hash_iterations",
|
||||||
|
"roles",
|
||||||
|
],
|
||||||
|
uiSchema: {
|
||||||
|
reset_admin_password: {
|
||||||
|
"ui:widget": "switch",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tls: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["enabled", "cert", "key"],
|
||||||
|
advancedFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
networking: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["ipv6"],
|
||||||
|
advancedFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"header_map",
|
||||||
|
"logout_url",
|
||||||
|
"auth_secret",
|
||||||
|
"default_role",
|
||||||
|
"separator",
|
||||||
|
],
|
||||||
|
advancedFields: ["header_map", "auth_secret", "separator"],
|
||||||
|
liveValidate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
logger: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["default", "logs"],
|
||||||
|
advancedFields: ["logs"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
environment_vars: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [],
|
||||||
|
advancedFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
telemetry: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: ["network_interfaces", "stats", "version_check"],
|
||||||
|
advancedFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
detectors: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [],
|
||||||
|
advancedFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"path",
|
||||||
|
"labelmap_path",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"input_pixel_format",
|
||||||
|
"input_tensor",
|
||||||
|
"input_dtype",
|
||||||
|
"model_type",
|
||||||
|
],
|
||||||
|
advancedFields: [
|
||||||
|
"input_pixel_format",
|
||||||
|
"input_tensor",
|
||||||
|
"input_dtype",
|
||||||
|
"model_type",
|
||||||
|
],
|
||||||
|
hiddenFields: ["labelmap", "attributes_map"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
genai: {
|
||||||
|
base: {
|
||||||
|
fieldOrder: [
|
||||||
|
"provider",
|
||||||
|
"api_key",
|
||||||
|
"base_url",
|
||||||
|
"model",
|
||||||
|
"provider_options",
|
||||||
|
"runtime_options",
|
||||||
|
],
|
||||||
|
advancedFields: ["base_url", "provider_options", "runtime_options"],
|
||||||
|
hiddenFields: ["genai.enabled_in_config"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
classification: {
|
||||||
|
base: {
|
||||||
|
hiddenFields: ["custom"],
|
||||||
|
advancedFields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeSectionConfig = (
|
||||||
|
base: SectionConfig | undefined,
|
||||||
|
overrides: Partial<SectionConfig> | undefined,
|
||||||
|
): SectionConfig =>
|
||||||
|
mergeWith({}, base ?? {}, overrides ?? {}, (objValue, srcValue, key) => {
|
||||||
|
if (Array.isArray(objValue) || Array.isArray(srcValue)) {
|
||||||
|
return srcValue ?? objValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "uiSchema" && srcValue !== undefined) {
|
||||||
|
return srcValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
export function getSectionConfig(
|
||||||
|
sectionKey: string,
|
||||||
|
level: "global" | "camera",
|
||||||
|
): SectionConfig {
|
||||||
|
const entry = sectionConfigs[sectionKey];
|
||||||
|
if (!entry) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const overrides = level === "global" ? entry.global : entry.camera;
|
||||||
|
return mergeSectionConfig(entry.base, overrides);
|
||||||
|
}
|
||||||
@ -2,30 +2,11 @@
|
|||||||
// Reusable for both global and camera-level audio settings
|
// Reusable for both global and camera-level audio settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const AudioSection = createConfigSection({
|
export const AudioSection = createConfigSection({
|
||||||
sectionPath: "audio",
|
sectionPath: "audio",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("audio", "camera"),
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"listen",
|
|
||||||
"filters",
|
|
||||||
"min_volume",
|
|
||||||
"max_not_heard",
|
|
||||||
"num_threads",
|
|
||||||
],
|
|
||||||
fieldGroups: {
|
|
||||||
detection: ["enabled", "listen", "filters"],
|
|
||||||
sensitivity: ["min_volume", "max_not_heard"],
|
|
||||||
},
|
|
||||||
hiddenFields: ["enabled_in_config"],
|
|
||||||
advancedFields: ["min_volume", "max_not_heard", "num_threads"],
|
|
||||||
uiSchema: {
|
|
||||||
listen: {
|
|
||||||
"ui:widget": "audioLabels",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default AudioSection;
|
export default AudioSection;
|
||||||
|
|||||||
@ -2,15 +2,11 @@
|
|||||||
// Global and camera-level audio transcription settings
|
// Global and camera-level audio transcription settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const AudioTranscriptionSection = createConfigSection({
|
export const AudioTranscriptionSection = createConfigSection({
|
||||||
sectionPath: "audio_transcription",
|
sectionPath: "audio_transcription",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("audio_transcription", "camera"),
|
||||||
fieldOrder: ["enabled", "language", "device", "model_size", "live_enabled"],
|
|
||||||
hiddenFields: ["enabled_in_config"],
|
|
||||||
advancedFields: ["language", "device", "model_size"],
|
|
||||||
overrideFields: ["enabled", "live_enabled"],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default AudioTranscriptionSection;
|
export default AudioTranscriptionSection;
|
||||||
|
|||||||
12
web/src/components/config-form/sections/AuthSection.tsx
Normal file
12
web/src/components/config-form/sections/AuthSection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Auth Section Component
|
||||||
|
// Global authentication configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const AuthSection = createConfigSection({
|
||||||
|
sectionPath: "auth",
|
||||||
|
defaultConfig: getSectionConfig("auth", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AuthSection;
|
||||||
@ -2,15 +2,11 @@
|
|||||||
// Camera-level birdseye settings
|
// Camera-level birdseye settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const BirdseyeSection = createConfigSection({
|
export const BirdseyeSection = createConfigSection({
|
||||||
sectionPath: "birdseye",
|
sectionPath: "birdseye",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("birdseye", "camera"),
|
||||||
fieldOrder: ["enabled", "mode", "order"],
|
|
||||||
hiddenFields: [],
|
|
||||||
advancedFields: [],
|
|
||||||
overrideFields: ["enabled", "mode"],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default BirdseyeSection;
|
export default BirdseyeSection;
|
||||||
|
|||||||
@ -2,28 +2,11 @@
|
|||||||
// Camera-specific MQTT image publishing settings
|
// Camera-specific MQTT image publishing settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const CameraMqttSection = createConfigSection({
|
export const CameraMqttSection = createConfigSection({
|
||||||
sectionPath: "mqtt",
|
sectionPath: "mqtt",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("mqtt", "camera"),
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"timestamp",
|
|
||||||
"bounding_box",
|
|
||||||
"crop",
|
|
||||||
"height",
|
|
||||||
"required_zones",
|
|
||||||
"quality",
|
|
||||||
],
|
|
||||||
hiddenFields: [],
|
|
||||||
advancedFields: ["height", "quality"],
|
|
||||||
overrideFields: [],
|
|
||||||
uiSchema: {
|
|
||||||
required_zones: {
|
|
||||||
"ui:widget": "zoneNames",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default CameraMqttSection;
|
export default CameraMqttSection;
|
||||||
|
|||||||
@ -2,15 +2,11 @@
|
|||||||
// Camera UI display settings
|
// Camera UI display settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const CameraUiSection = createConfigSection({
|
export const CameraUiSection = createConfigSection({
|
||||||
sectionPath: "ui",
|
sectionPath: "ui",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("ui", "camera"),
|
||||||
fieldOrder: ["dashboard", "order"],
|
|
||||||
hiddenFields: [],
|
|
||||||
advancedFields: [],
|
|
||||||
overrideFields: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default CameraUiSection;
|
export default CameraUiSection;
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
// Classification Section Component
|
||||||
|
// Global classification configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const ClassificationSection = createConfigSection({
|
||||||
|
sectionPath: "classification",
|
||||||
|
defaultConfig: getSectionConfig("classification", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ClassificationSection;
|
||||||
12
web/src/components/config-form/sections/DatabaseSection.tsx
Normal file
12
web/src/components/config-form/sections/DatabaseSection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Database Section Component
|
||||||
|
// Global database configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const DatabaseSection = createConfigSection({
|
||||||
|
sectionPath: "database",
|
||||||
|
defaultConfig: getSectionConfig("database", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default DatabaseSection;
|
||||||
@ -2,32 +2,11 @@
|
|||||||
// Reusable for both global and camera-level detect settings
|
// Reusable for both global and camera-level detect settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const DetectSection = createConfigSection({
|
export const DetectSection = createConfigSection({
|
||||||
sectionPath: "detect",
|
sectionPath: "detect",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("detect", "camera"),
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"fps",
|
|
||||||
"width",
|
|
||||||
"height",
|
|
||||||
"min_initialized",
|
|
||||||
"max_disappeared",
|
|
||||||
"annotation_offset",
|
|
||||||
"stationary",
|
|
||||||
],
|
|
||||||
fieldGroups: {
|
|
||||||
resolution: ["enabled", "width", "height"],
|
|
||||||
tracking: ["min_initialized", "max_disappeared"],
|
|
||||||
},
|
|
||||||
hiddenFields: ["enabled_in_config"],
|
|
||||||
advancedFields: [
|
|
||||||
"min_initialized",
|
|
||||||
"max_disappeared",
|
|
||||||
"annotation_offset",
|
|
||||||
"stationary",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default DetectSection;
|
export default DetectSection;
|
||||||
|
|||||||
12
web/src/components/config-form/sections/DetectorsSection.tsx
Normal file
12
web/src/components/config-form/sections/DetectorsSection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Detectors Section Component
|
||||||
|
// Global detectors configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const DetectorsSection = createConfigSection({
|
||||||
|
sectionPath: "detectors",
|
||||||
|
defaultConfig: getSectionConfig("detectors", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default DetectorsSection;
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
// Environment Variables Section Component
|
||||||
|
// Global environment variables configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const EnvironmentVarsSection = createConfigSection({
|
||||||
|
sectionPath: "environment_vars",
|
||||||
|
defaultConfig: getSectionConfig("environment_vars", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default EnvironmentVarsSection;
|
||||||
@ -2,15 +2,11 @@
|
|||||||
// Camera-level face recognition settings
|
// Camera-level face recognition settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const FaceRecognitionSection = createConfigSection({
|
export const FaceRecognitionSection = createConfigSection({
|
||||||
sectionPath: "face_recognition",
|
sectionPath: "face_recognition",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("face_recognition", "camera"),
|
||||||
fieldOrder: ["enabled", "min_area"],
|
|
||||||
hiddenFields: [],
|
|
||||||
advancedFields: ["min_area"],
|
|
||||||
overrideFields: ["enabled", "min_area"],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default FaceRecognitionSection;
|
export default FaceRecognitionSection;
|
||||||
|
|||||||
@ -2,136 +2,11 @@
|
|||||||
// Global and camera-level FFmpeg settings
|
// Global and camera-level FFmpeg settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const FfmpegSection = createConfigSection({
|
export const FfmpegSection = createConfigSection({
|
||||||
sectionPath: "ffmpeg",
|
sectionPath: "ffmpeg",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("ffmpeg", "camera"),
|
||||||
fieldOrder: [
|
|
||||||
"inputs",
|
|
||||||
"path",
|
|
||||||
"global_args",
|
|
||||||
"hwaccel_args",
|
|
||||||
"input_args",
|
|
||||||
"output_args",
|
|
||||||
"retry_interval",
|
|
||||||
"apple_compatibility",
|
|
||||||
"gpu",
|
|
||||||
],
|
|
||||||
hiddenFields: [],
|
|
||||||
advancedFields: [
|
|
||||||
"global_args",
|
|
||||||
"hwaccel_args",
|
|
||||||
"input_args",
|
|
||||||
"output_args",
|
|
||||||
"retry_interval",
|
|
||||||
"apple_compatibility",
|
|
||||||
"gpu",
|
|
||||||
],
|
|
||||||
overrideFields: [
|
|
||||||
"path",
|
|
||||||
"global_args",
|
|
||||||
"hwaccel_args",
|
|
||||||
"input_args",
|
|
||||||
"output_args",
|
|
||||||
"retry_interval",
|
|
||||||
"apple_compatibility",
|
|
||||||
"gpu",
|
|
||||||
],
|
|
||||||
uiSchema: {
|
|
||||||
global_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hwaccel_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
input_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
output_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
detect: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
record: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
detect: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
record: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputs: {
|
|
||||||
items: {
|
|
||||||
global_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hwaccel_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
input_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
output_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
detect: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
record: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default FfmpegSection;
|
export default FfmpegSection;
|
||||||
|
|||||||
12
web/src/components/config-form/sections/GenaiSection.tsx
Normal file
12
web/src/components/config-form/sections/GenaiSection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// GenAI Section Component
|
||||||
|
// Global GenAI configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const GenaiSection = createConfigSection({
|
||||||
|
sectionPath: "genai",
|
||||||
|
defaultConfig: getSectionConfig("genai", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default GenaiSection;
|
||||||
@ -2,15 +2,11 @@
|
|||||||
// Reusable for both global and camera-level live settings
|
// Reusable for both global and camera-level live settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const LiveSection = createConfigSection({
|
export const LiveSection = createConfigSection({
|
||||||
sectionPath: "live",
|
sectionPath: "live",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("live", "camera"),
|
||||||
fieldOrder: ["stream_name", "height", "quality"],
|
|
||||||
fieldGroups: {},
|
|
||||||
hiddenFields: ["enabled_in_config"],
|
|
||||||
advancedFields: ["quality"],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default LiveSection;
|
export default LiveSection;
|
||||||
|
|||||||
12
web/src/components/config-form/sections/LoggerSection.tsx
Normal file
12
web/src/components/config-form/sections/LoggerSection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Logger Section Component
|
||||||
|
// Global logger configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const LoggerSection = createConfigSection({
|
||||||
|
sectionPath: "logger",
|
||||||
|
defaultConfig: getSectionConfig("logger", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LoggerSection;
|
||||||
@ -2,15 +2,11 @@
|
|||||||
// Camera-level LPR settings
|
// Camera-level LPR settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const LprSection = createConfigSection({
|
export const LprSection = createConfigSection({
|
||||||
sectionPath: "lpr",
|
sectionPath: "lpr",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("lpr", "camera"),
|
||||||
fieldOrder: ["enabled", "expire_time", "min_area", "enhancement"],
|
|
||||||
hiddenFields: [],
|
|
||||||
advancedFields: ["expire_time", "min_area", "enhancement"],
|
|
||||||
overrideFields: ["enabled", "min_area", "enhancement"],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default LprSection;
|
export default LprSection;
|
||||||
|
|||||||
12
web/src/components/config-form/sections/ModelSection.tsx
Normal file
12
web/src/components/config-form/sections/ModelSection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Model Section Component
|
||||||
|
// Global model configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const ModelSection = createConfigSection({
|
||||||
|
sectionPath: "model",
|
||||||
|
defaultConfig: getSectionConfig("model", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ModelSection;
|
||||||
@ -2,35 +2,11 @@
|
|||||||
// Reusable for both global and camera-level motion settings
|
// Reusable for both global and camera-level motion settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const MotionSection = createConfigSection({
|
export const MotionSection = createConfigSection({
|
||||||
sectionPath: "motion",
|
sectionPath: "motion",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("motion", "camera"),
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"threshold",
|
|
||||||
"lightning_threshold",
|
|
||||||
"improve_contrast",
|
|
||||||
"contour_area",
|
|
||||||
"delta_alpha",
|
|
||||||
"frame_alpha",
|
|
||||||
"frame_height",
|
|
||||||
"mask",
|
|
||||||
"mqtt_off_delay",
|
|
||||||
],
|
|
||||||
fieldGroups: {
|
|
||||||
sensitivity: ["enabled", "threshold", "contour_area"],
|
|
||||||
algorithm: ["improve_contrast", "delta_alpha", "frame_alpha"],
|
|
||||||
},
|
|
||||||
hiddenFields: ["enabled_in_config", "mask", "raw_mask"],
|
|
||||||
advancedFields: [
|
|
||||||
"lightning_threshold",
|
|
||||||
"delta_alpha",
|
|
||||||
"frame_alpha",
|
|
||||||
"frame_height",
|
|
||||||
"mqtt_off_delay",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default MotionSection;
|
export default MotionSection;
|
||||||
|
|||||||
12
web/src/components/config-form/sections/MqttSection.tsx
Normal file
12
web/src/components/config-form/sections/MqttSection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// MQTT Section Component
|
||||||
|
// Global MQTT configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const MqttSection = createConfigSection({
|
||||||
|
sectionPath: "mqtt",
|
||||||
|
defaultConfig: getSectionConfig("mqtt", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default MqttSection;
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
// Networking Section Component
|
||||||
|
// Global networking configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const NetworkingSection = createConfigSection({
|
||||||
|
sectionPath: "networking",
|
||||||
|
defaultConfig: getSectionConfig("networking", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default NetworkingSection;
|
||||||
@ -2,15 +2,11 @@
|
|||||||
// Reusable for both global and camera-level notification settings
|
// Reusable for both global and camera-level notification settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const NotificationsSection = createConfigSection({
|
export const NotificationsSection = createConfigSection({
|
||||||
sectionPath: "notifications",
|
sectionPath: "notifications",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("notifications", "camera"),
|
||||||
fieldOrder: ["enabled", "email"],
|
|
||||||
fieldGroups: {},
|
|
||||||
hiddenFields: ["enabled_in_config"],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default NotificationsSection;
|
export default NotificationsSection;
|
||||||
|
|||||||
@ -2,60 +2,11 @@
|
|||||||
// Reusable for both global and camera-level objects settings
|
// Reusable for both global and camera-level objects settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const ObjectsSection = createConfigSection({
|
export const ObjectsSection = createConfigSection({
|
||||||
sectionPath: "objects",
|
sectionPath: "objects",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("objects", "camera"),
|
||||||
fieldOrder: ["track", "alert", "detect", "filters"],
|
|
||||||
fieldGroups: {
|
|
||||||
tracking: ["track", "alert", "detect"],
|
|
||||||
filtering: ["filters"],
|
|
||||||
},
|
|
||||||
hiddenFields: [
|
|
||||||
"enabled_in_config",
|
|
||||||
"mask",
|
|
||||||
"raw_mask",
|
|
||||||
"genai.enabled_in_config",
|
|
||||||
"filters.*.mask",
|
|
||||||
"filters.*.raw_mask",
|
|
||||||
],
|
|
||||||
advancedFields: ["filters"],
|
|
||||||
uiSchema: {
|
|
||||||
"filters.*.min_area": {
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"filters.*.max_area": {
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
track: {
|
|
||||||
"ui:widget": "objectLabels",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
genai: {
|
|
||||||
objects: {
|
|
||||||
"ui:widget": "objectLabels",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required_zones: {
|
|
||||||
"ui:widget": "zoneNames",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
enabled_in_config: {
|
|
||||||
"ui:widget": "hidden",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ObjectsSection;
|
export default ObjectsSection;
|
||||||
|
|||||||
@ -2,36 +2,11 @@
|
|||||||
// Camera-level ONVIF and autotracking settings
|
// Camera-level ONVIF and autotracking settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const OnvifSection = createConfigSection({
|
export const OnvifSection = createConfigSection({
|
||||||
sectionPath: "onvif",
|
sectionPath: "onvif",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("onvif", "camera"),
|
||||||
fieldOrder: [
|
|
||||||
"host",
|
|
||||||
"port",
|
|
||||||
"user",
|
|
||||||
"password",
|
|
||||||
"tls_insecure",
|
|
||||||
"ignore_time_mismatch",
|
|
||||||
"autotracking",
|
|
||||||
],
|
|
||||||
hiddenFields: [
|
|
||||||
"autotracking.enabled_in_config",
|
|
||||||
"autotracking.movement_weights",
|
|
||||||
],
|
|
||||||
advancedFields: ["tls_insecure", "ignore_time_mismatch"],
|
|
||||||
overrideFields: [],
|
|
||||||
uiSchema: {
|
|
||||||
autotracking: {
|
|
||||||
required_zones: {
|
|
||||||
"ui:widget": "zoneNames",
|
|
||||||
},
|
|
||||||
track: {
|
|
||||||
"ui:widget": "objectLabels",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default OnvifSection;
|
export default OnvifSection;
|
||||||
|
|||||||
12
web/src/components/config-form/sections/ProxySection.tsx
Normal file
12
web/src/components/config-form/sections/ProxySection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Proxy Section Component
|
||||||
|
// Global proxy configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const ProxySection = createConfigSection({
|
||||||
|
sectionPath: "proxy",
|
||||||
|
defaultConfig: getSectionConfig("proxy", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ProxySection;
|
||||||
@ -2,27 +2,11 @@
|
|||||||
// Reusable for both global and camera-level record settings
|
// Reusable for both global and camera-level record settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const RecordSection = createConfigSection({
|
export const RecordSection = createConfigSection({
|
||||||
sectionPath: "record",
|
sectionPath: "record",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("record", "camera"),
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"expire_interval",
|
|
||||||
"continuous",
|
|
||||||
"motion",
|
|
||||||
"alerts",
|
|
||||||
"detections",
|
|
||||||
"preview",
|
|
||||||
"export",
|
|
||||||
],
|
|
||||||
fieldGroups: {
|
|
||||||
retention: ["enabled", "continuous", "motion"],
|
|
||||||
events: ["alerts", "detections"],
|
|
||||||
},
|
|
||||||
hiddenFields: ["enabled_in_config", "sync_recordings"],
|
|
||||||
advancedFields: ["expire_interval", "preview", "export"],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default RecordSection;
|
export default RecordSection;
|
||||||
|
|||||||
@ -2,34 +2,11 @@
|
|||||||
// Reusable for both global and camera-level review settings
|
// Reusable for both global and camera-level review settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const ReviewSection = createConfigSection({
|
export const ReviewSection = createConfigSection({
|
||||||
sectionPath: "review",
|
sectionPath: "review",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("review", "camera"),
|
||||||
fieldOrder: ["alerts", "detections", "genai"],
|
|
||||||
fieldGroups: {},
|
|
||||||
hiddenFields: [
|
|
||||||
"enabled_in_config",
|
|
||||||
"alerts.labels",
|
|
||||||
"alerts.enabled_in_config",
|
|
||||||
"alerts.required_zones",
|
|
||||||
"detections.labels",
|
|
||||||
"detections.enabled_in_config",
|
|
||||||
"detections.required_zones",
|
|
||||||
"genai.enabled_in_config",
|
|
||||||
],
|
|
||||||
advancedFields: [],
|
|
||||||
uiSchema: {
|
|
||||||
genai: {
|
|
||||||
additional_concerns: {
|
|
||||||
"ui:widget": "textarea",
|
|
||||||
},
|
|
||||||
activity_context_prompt: {
|
|
||||||
"ui:widget": "textarea",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ReviewSection;
|
export default ReviewSection;
|
||||||
|
|||||||
@ -2,15 +2,11 @@
|
|||||||
// Camera-level semantic search trigger settings
|
// Camera-level semantic search trigger settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const SemanticSearchSection = createConfigSection({
|
export const SemanticSearchSection = createConfigSection({
|
||||||
sectionPath: "semantic_search",
|
sectionPath: "semantic_search",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("semantic_search", "camera"),
|
||||||
fieldOrder: ["triggers"],
|
|
||||||
hiddenFields: [],
|
|
||||||
advancedFields: [],
|
|
||||||
overrideFields: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SemanticSearchSection;
|
export default SemanticSearchSection;
|
||||||
|
|||||||
@ -2,32 +2,11 @@
|
|||||||
// Reusable for both global and camera-level snapshots settings
|
// Reusable for both global and camera-level snapshots settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const SnapshotsSection = createConfigSection({
|
export const SnapshotsSection = createConfigSection({
|
||||||
sectionPath: "snapshots",
|
sectionPath: "snapshots",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("snapshots", "camera"),
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"bounding_box",
|
|
||||||
"crop",
|
|
||||||
"quality",
|
|
||||||
"timestamp",
|
|
||||||
"retain",
|
|
||||||
],
|
|
||||||
fieldGroups: {
|
|
||||||
display: ["enabled", "bounding_box", "crop", "quality", "timestamp"],
|
|
||||||
},
|
|
||||||
hiddenFields: ["enabled_in_config"],
|
|
||||||
advancedFields: ["quality", "retain"],
|
|
||||||
uiSchema: {
|
|
||||||
required_zones: {
|
|
||||||
"ui:widget": "zoneNames",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SnapshotsSection;
|
export default SnapshotsSection;
|
||||||
|
|||||||
12
web/src/components/config-form/sections/TelemetrySection.tsx
Normal file
12
web/src/components/config-form/sections/TelemetrySection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Telemetry Section Component
|
||||||
|
// Global telemetry configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const TelemetrySection = createConfigSection({
|
||||||
|
sectionPath: "telemetry",
|
||||||
|
defaultConfig: getSectionConfig("telemetry", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TelemetrySection;
|
||||||
@ -2,14 +2,11 @@
|
|||||||
// Reusable for both global and camera-level timestamp_style settings
|
// Reusable for both global and camera-level timestamp_style settings
|
||||||
|
|
||||||
import { createConfigSection } from "./BaseSection";
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
export const TimestampSection = createConfigSection({
|
export const TimestampSection = createConfigSection({
|
||||||
sectionPath: "timestamp_style",
|
sectionPath: "timestamp_style",
|
||||||
defaultConfig: {
|
defaultConfig: getSectionConfig("timestamp_style", "camera"),
|
||||||
fieldOrder: ["position", "format", "color", "thickness"],
|
|
||||||
hiddenFields: ["effect", "enabled_in_config"],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default TimestampSection;
|
export default TimestampSection;
|
||||||
|
|||||||
12
web/src/components/config-form/sections/TlsSection.tsx
Normal file
12
web/src/components/config-form/sections/TlsSection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// TLS Section Component
|
||||||
|
// Global TLS configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const TlsSection = createConfigSection({
|
||||||
|
sectionPath: "tls",
|
||||||
|
defaultConfig: getSectionConfig("tls", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TlsSection;
|
||||||
12
web/src/components/config-form/sections/UiSection.tsx
Normal file
12
web/src/components/config-form/sections/UiSection.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// UI Section Component
|
||||||
|
// Global UI configuration settings
|
||||||
|
|
||||||
|
import { createConfigSection } from "./BaseSection";
|
||||||
|
import { getSectionConfig } from "../sectionConfigs";
|
||||||
|
|
||||||
|
export const UiSection = createConfigSection({
|
||||||
|
sectionPath: "ui",
|
||||||
|
defaultConfig: getSectionConfig("ui", "global"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default UiSection;
|
||||||
@ -17,13 +17,27 @@ export { ReviewSection } from "./ReviewSection";
|
|||||||
export { AudioSection } from "./AudioSection";
|
export { AudioSection } from "./AudioSection";
|
||||||
export { AudioTranscriptionSection } from "./AudioTranscriptionSection";
|
export { AudioTranscriptionSection } from "./AudioTranscriptionSection";
|
||||||
export { BirdseyeSection } from "./BirdseyeSection";
|
export { BirdseyeSection } from "./BirdseyeSection";
|
||||||
|
export { AuthSection } from "./AuthSection";
|
||||||
|
export { ClassificationSection } from "./ClassificationSection";
|
||||||
export { CameraMqttSection } from "./CameraMqttSection";
|
export { CameraMqttSection } from "./CameraMqttSection";
|
||||||
export { CameraUiSection } from "./CameraUiSection";
|
export { CameraUiSection } from "./CameraUiSection";
|
||||||
|
export { DatabaseSection } from "./DatabaseSection";
|
||||||
|
export { DetectorsSection } from "./DetectorsSection";
|
||||||
|
export { EnvironmentVarsSection } from "./EnvironmentVarsSection";
|
||||||
export { FaceRecognitionSection } from "./FaceRecognitionSection";
|
export { FaceRecognitionSection } from "./FaceRecognitionSection";
|
||||||
export { FfmpegSection } from "./FfmpegSection";
|
export { FfmpegSection } from "./FfmpegSection";
|
||||||
|
export { GenaiSection } from "./GenaiSection";
|
||||||
export { LprSection } from "./LprSection";
|
export { LprSection } from "./LprSection";
|
||||||
|
export { LoggerSection } from "./LoggerSection";
|
||||||
export { NotificationsSection } from "./NotificationsSection";
|
export { NotificationsSection } from "./NotificationsSection";
|
||||||
export { OnvifSection } from "./OnvifSection";
|
export { OnvifSection } from "./OnvifSection";
|
||||||
export { LiveSection } from "./LiveSection";
|
export { LiveSection } from "./LiveSection";
|
||||||
|
export { ModelSection } from "./ModelSection";
|
||||||
|
export { MqttSection } from "./MqttSection";
|
||||||
|
export { NetworkingSection } from "./NetworkingSection";
|
||||||
|
export { ProxySection } from "./ProxySection";
|
||||||
export { SemanticSearchSection } from "./SemanticSearchSection";
|
export { SemanticSearchSection } from "./SemanticSearchSection";
|
||||||
|
export { TelemetrySection } from "./TelemetrySection";
|
||||||
export { TimestampSection } from "./TimestampSection";
|
export { TimestampSection } from "./TimestampSection";
|
||||||
|
export { TlsSection } from "./TlsSection";
|
||||||
|
export { UiSection } from "./UiSection";
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import FrigatePlusSettingsView from "@/views/settings/FrigatePlusSettingsView";
|
|||||||
import MaintenanceSettingsView from "@/views/settings/MaintenanceSettingsView";
|
import MaintenanceSettingsView from "@/views/settings/MaintenanceSettingsView";
|
||||||
import GlobalConfigView from "@/views/settings/GlobalConfigView";
|
import GlobalConfigView from "@/views/settings/GlobalConfigView";
|
||||||
import CameraConfigView from "@/views/settings/CameraConfigView";
|
import CameraConfigView from "@/views/settings/CameraConfigView";
|
||||||
|
import { createSingleSectionPage } from "@/views/settings/SingleSectionPage";
|
||||||
import { useSearchEffect } from "@/hooks/use-overlay-state";
|
import { useSearchEffect } from "@/hooks/use-overlay-state";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { useInitialCameraState } from "@/api/ws";
|
import { useInitialCameraState } from "@/api/ws";
|
||||||
@ -64,6 +65,10 @@ import { cn } from "@/lib/utils";
|
|||||||
import Heading from "@/components/ui/heading";
|
import Heading from "@/components/ui/heading";
|
||||||
import { LuChevronRight } from "react-icons/lu";
|
import { LuChevronRight } from "react-icons/lu";
|
||||||
import Logo from "@/components/Logo";
|
import Logo from "@/components/Logo";
|
||||||
|
import {
|
||||||
|
CameraMqttSection,
|
||||||
|
MqttSection,
|
||||||
|
} from "@/components/config-form/sections";
|
||||||
import {
|
import {
|
||||||
MobilePage,
|
MobilePage,
|
||||||
MobilePageContent,
|
MobilePageContent,
|
||||||
@ -74,7 +79,9 @@ import {
|
|||||||
const allSettingsViews = [
|
const allSettingsViews = [
|
||||||
"ui",
|
"ui",
|
||||||
"globalConfig",
|
"globalConfig",
|
||||||
|
"mqtt",
|
||||||
"cameraConfig",
|
"cameraConfig",
|
||||||
|
"cameraMqtt",
|
||||||
"enrichments",
|
"enrichments",
|
||||||
"cameraManagement",
|
"cameraManagement",
|
||||||
"cameraReview",
|
"cameraReview",
|
||||||
@ -90,18 +97,33 @@ const allSettingsViews = [
|
|||||||
] as const;
|
] as const;
|
||||||
type SettingsType = (typeof allSettingsViews)[number];
|
type SettingsType = (typeof allSettingsViews)[number];
|
||||||
|
|
||||||
|
const MqttSettingsPage = createSingleSectionPage({
|
||||||
|
sectionKey: "mqtt",
|
||||||
|
level: "global",
|
||||||
|
SectionComponent: MqttSection,
|
||||||
|
});
|
||||||
|
|
||||||
|
const CameraMqttSettingsPage = createSingleSectionPage({
|
||||||
|
sectionKey: "mqtt",
|
||||||
|
level: "camera",
|
||||||
|
SectionComponent: CameraMqttSection,
|
||||||
|
showOverrideIndicator: false,
|
||||||
|
});
|
||||||
|
|
||||||
const settingsGroups = [
|
const settingsGroups = [
|
||||||
{
|
{
|
||||||
label: "general",
|
label: "general",
|
||||||
items: [
|
items: [
|
||||||
{ key: "ui", component: UiSettingsView },
|
{ key: "ui", component: UiSettingsView },
|
||||||
{ key: "globalConfig", component: GlobalConfigView },
|
{ key: "globalConfig", component: GlobalConfigView },
|
||||||
|
{ key: "mqtt", component: MqttSettingsPage },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "cameras",
|
label: "cameras",
|
||||||
items: [
|
items: [
|
||||||
{ key: "cameraConfig", component: CameraConfigView },
|
{ key: "cameraConfig", component: CameraConfigView },
|
||||||
|
{ key: "cameraMqtt", component: CameraMqttSettingsPage },
|
||||||
{ key: "cameraManagement", component: CameraManagementView },
|
{ key: "cameraManagement", component: CameraManagementView },
|
||||||
{ key: "cameraReview", component: CameraReviewSettingsView },
|
{ key: "cameraReview", component: CameraReviewSettingsView },
|
||||||
{ key: "masksAndZones", component: MasksAndZonesView },
|
{ key: "masksAndZones", component: MasksAndZonesView },
|
||||||
@ -139,6 +161,7 @@ const settingsGroups = [
|
|||||||
const CAMERA_SELECT_BUTTON_PAGES = [
|
const CAMERA_SELECT_BUTTON_PAGES = [
|
||||||
"debug",
|
"debug",
|
||||||
"cameraConfig",
|
"cameraConfig",
|
||||||
|
"cameraMqtt",
|
||||||
"cameraReview",
|
"cameraReview",
|
||||||
"masksAndZones",
|
"masksAndZones",
|
||||||
"motionTuner",
|
"motionTuner",
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
// Global Configuration View
|
// Global Configuration View
|
||||||
// Main view for configuring global Frigate settings
|
// Main view for configuring global Frigate settings
|
||||||
|
|
||||||
import { useMemo, useCallback, useState, useEffect, useRef } from "react";
|
import { useMemo, useCallback, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import axios from "axios";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ConfigForm } from "@/components/config-form/ConfigForm";
|
|
||||||
import { DetectSection } from "@/components/config-form/sections/DetectSection";
|
import { DetectSection } from "@/components/config-form/sections/DetectSection";
|
||||||
import { RecordSection } from "@/components/config-form/sections/RecordSection";
|
import { RecordSection } from "@/components/config-form/sections/RecordSection";
|
||||||
import { SnapshotsSection } from "@/components/config-form/sections/SnapshotsSection";
|
import { SnapshotsSection } from "@/components/config-form/sections/SnapshotsSection";
|
||||||
@ -14,545 +11,90 @@ import { MotionSection } from "@/components/config-form/sections/MotionSection";
|
|||||||
import { ObjectsSection } from "@/components/config-form/sections/ObjectsSection";
|
import { ObjectsSection } from "@/components/config-form/sections/ObjectsSection";
|
||||||
import { ReviewSection } from "@/components/config-form/sections/ReviewSection";
|
import { ReviewSection } from "@/components/config-form/sections/ReviewSection";
|
||||||
import { AudioSection } from "@/components/config-form/sections/AudioSection";
|
import { AudioSection } from "@/components/config-form/sections/AudioSection";
|
||||||
|
import { AudioTranscriptionSection } from "@/components/config-form/sections/AudioTranscriptionSection";
|
||||||
|
import { AuthSection } from "@/components/config-form/sections/AuthSection";
|
||||||
|
import { BirdseyeSection } from "@/components/config-form/sections/BirdseyeSection";
|
||||||
|
import { ClassificationSection } from "@/components/config-form/sections/ClassificationSection";
|
||||||
|
import { DatabaseSection } from "@/components/config-form/sections/DatabaseSection";
|
||||||
|
import { DetectorsSection } from "@/components/config-form/sections/DetectorsSection";
|
||||||
|
import { EnvironmentVarsSection } from "@/components/config-form/sections/EnvironmentVarsSection";
|
||||||
|
import { FaceRecognitionSection } from "@/components/config-form/sections/FaceRecognitionSection";
|
||||||
|
import { FfmpegSection } from "@/components/config-form/sections/FfmpegSection";
|
||||||
|
import { GenaiSection } from "@/components/config-form/sections/GenaiSection";
|
||||||
import { LiveSection } from "@/components/config-form/sections/LiveSection";
|
import { LiveSection } from "@/components/config-form/sections/LiveSection";
|
||||||
|
import { LoggerSection } from "@/components/config-form/sections/LoggerSection";
|
||||||
|
import { LprSection } from "@/components/config-form/sections/LprSection";
|
||||||
|
import { ModelSection } from "@/components/config-form/sections/ModelSection";
|
||||||
|
import { MqttSection } from "@/components/config-form/sections/MqttSection";
|
||||||
|
import { NetworkingSection } from "@/components/config-form/sections/NetworkingSection";
|
||||||
|
import { ProxySection } from "@/components/config-form/sections/ProxySection";
|
||||||
|
import { SemanticSearchSection } from "@/components/config-form/sections/SemanticSearchSection";
|
||||||
import { TimestampSection } from "@/components/config-form/sections/TimestampSection";
|
import { TimestampSection } from "@/components/config-form/sections/TimestampSection";
|
||||||
import type { RJSFSchema } from "@rjsf/utils";
|
import { TelemetrySection } from "@/components/config-form/sections/TelemetrySection";
|
||||||
|
import { TlsSection } from "@/components/config-form/sections/TlsSection";
|
||||||
|
import { UiSection } from "@/components/config-form/sections/UiSection";
|
||||||
import type { FrigateConfig } from "@/types/frigateConfig";
|
import type { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { extractSchemaSection } from "@/lib/config-schema";
|
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
import Heading from "@/components/ui/heading";
|
import Heading from "@/components/ui/heading";
|
||||||
import { LuSave } from "react-icons/lu";
|
|
||||||
import isEqual from "lodash/isEqual";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { getSectionConfig } from "@/components/config-form/sectionConfigs";
|
||||||
|
|
||||||
// Shared sections that can be overridden at camera level
|
// Shared sections that can be overridden at camera level
|
||||||
const sharedSections = [
|
const sharedSections = [
|
||||||
{ key: "detect", component: DetectSection },
|
{ key: "detect", component: DetectSection },
|
||||||
{ key: "record", component: RecordSection },
|
{ key: "record", component: RecordSection },
|
||||||
{
|
{ key: "snapshots", component: SnapshotsSection },
|
||||||
key: "snapshots",
|
|
||||||
component: SnapshotsSection,
|
|
||||||
},
|
|
||||||
{ key: "motion", component: MotionSection },
|
{ key: "motion", component: MotionSection },
|
||||||
{
|
{ key: "objects", component: ObjectsSection },
|
||||||
key: "objects",
|
|
||||||
component: ObjectsSection,
|
|
||||||
},
|
|
||||||
{ key: "review", component: ReviewSection },
|
{ key: "review", component: ReviewSection },
|
||||||
{ key: "audio", component: AudioSection },
|
{ key: "audio", component: AudioSection },
|
||||||
{ key: "live", component: LiveSection },
|
{ key: "live", component: LiveSection },
|
||||||
{
|
{ key: "timestamp_style", component: TimestampSection },
|
||||||
key: "timestamp_style",
|
|
||||||
component: TimestampSection,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Section configurations for global-only settings (system and integrations)
|
|
||||||
const globalSectionConfigs: Record<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
fieldOrder?: string[];
|
|
||||||
hiddenFields?: string[];
|
|
||||||
advancedFields?: string[];
|
|
||||||
liveValidate?: boolean;
|
|
||||||
uiSchema?: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
mqtt: {
|
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"host",
|
|
||||||
"port",
|
|
||||||
"user",
|
|
||||||
"password",
|
|
||||||
"topic_prefix",
|
|
||||||
"client_id",
|
|
||||||
"stats_interval",
|
|
||||||
"qos",
|
|
||||||
"tls_ca_certs",
|
|
||||||
"tls_client_cert",
|
|
||||||
"tls_client_key",
|
|
||||||
"tls_insecure",
|
|
||||||
],
|
|
||||||
advancedFields: [
|
|
||||||
"stats_interval",
|
|
||||||
"qos",
|
|
||||||
"tls_ca_certs",
|
|
||||||
"tls_client_cert",
|
|
||||||
"tls_client_key",
|
|
||||||
"tls_insecure",
|
|
||||||
],
|
|
||||||
liveValidate: true,
|
|
||||||
},
|
|
||||||
database: {
|
|
||||||
fieldOrder: ["path"],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
auth: {
|
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"reset_admin_password",
|
|
||||||
"cookie_name",
|
|
||||||
"cookie_secure",
|
|
||||||
"session_length",
|
|
||||||
"refresh_time",
|
|
||||||
"native_oauth_url",
|
|
||||||
"failed_login_rate_limit",
|
|
||||||
"trusted_proxies",
|
|
||||||
"hash_iterations",
|
|
||||||
"roles",
|
|
||||||
],
|
|
||||||
hiddenFields: ["admin_first_time_login"],
|
|
||||||
advancedFields: [
|
|
||||||
"cookie_name",
|
|
||||||
"cookie_secure",
|
|
||||||
"session_length",
|
|
||||||
"refresh_time",
|
|
||||||
"failed_login_rate_limit",
|
|
||||||
"trusted_proxies",
|
|
||||||
"hash_iterations",
|
|
||||||
"roles",
|
|
||||||
],
|
|
||||||
uiSchema: {
|
|
||||||
reset_admin_password: {
|
|
||||||
"ui:widget": "switch",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tls: {
|
|
||||||
fieldOrder: ["enabled", "cert", "key"],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
networking: {
|
|
||||||
fieldOrder: ["ipv6"],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
proxy: {
|
|
||||||
fieldOrder: [
|
|
||||||
"header_map",
|
|
||||||
"logout_url",
|
|
||||||
"auth_secret",
|
|
||||||
"default_role",
|
|
||||||
"separator",
|
|
||||||
],
|
|
||||||
advancedFields: ["header_map", "auth_secret", "separator"],
|
|
||||||
liveValidate: true,
|
|
||||||
},
|
|
||||||
ui: {
|
|
||||||
fieldOrder: [
|
|
||||||
"timezone",
|
|
||||||
"time_format",
|
|
||||||
"date_style",
|
|
||||||
"time_style",
|
|
||||||
"unit_system",
|
|
||||||
],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
logger: {
|
|
||||||
fieldOrder: ["default", "logs"],
|
|
||||||
advancedFields: ["logs"],
|
|
||||||
},
|
|
||||||
environment_vars: {
|
|
||||||
fieldOrder: [],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
telemetry: {
|
|
||||||
fieldOrder: ["network_interfaces", "stats", "version_check"],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
birdseye: {
|
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"restream",
|
|
||||||
"width",
|
|
||||||
"height",
|
|
||||||
"quality",
|
|
||||||
"mode",
|
|
||||||
"layout",
|
|
||||||
"inactivity_threshold",
|
|
||||||
"idle_heartbeat_fps",
|
|
||||||
],
|
|
||||||
advancedFields: ["width", "height", "quality", "inactivity_threshold"],
|
|
||||||
},
|
|
||||||
ffmpeg: {
|
|
||||||
fieldOrder: [
|
|
||||||
"path",
|
|
||||||
"global_args",
|
|
||||||
"hwaccel_args",
|
|
||||||
"input_args",
|
|
||||||
"output_args",
|
|
||||||
"retry_interval",
|
|
||||||
"apple_compatibility",
|
|
||||||
"gpu",
|
|
||||||
],
|
|
||||||
advancedFields: [
|
|
||||||
"global_args",
|
|
||||||
"hwaccel_args",
|
|
||||||
"input_args",
|
|
||||||
"output_args",
|
|
||||||
"retry_interval",
|
|
||||||
"apple_compatibility",
|
|
||||||
"gpu",
|
|
||||||
],
|
|
||||||
uiSchema: {
|
|
||||||
global_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hwaccel_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
input_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
output_args: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
detect: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
record: {
|
|
||||||
"ui:widget": "ArrayAsTextWidget",
|
|
||||||
"ui:options": {
|
|
||||||
suppressMultiSchema: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
detectors: {
|
|
||||||
fieldOrder: [],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
model: {
|
|
||||||
fieldOrder: [
|
|
||||||
"path",
|
|
||||||
"labelmap_path",
|
|
||||||
"width",
|
|
||||||
"height",
|
|
||||||
"input_pixel_format",
|
|
||||||
"input_tensor",
|
|
||||||
"input_dtype",
|
|
||||||
"model_type",
|
|
||||||
],
|
|
||||||
advancedFields: [
|
|
||||||
"input_pixel_format",
|
|
||||||
"input_tensor",
|
|
||||||
"input_dtype",
|
|
||||||
"model_type",
|
|
||||||
],
|
|
||||||
hiddenFields: ["labelmap", "attributes_map"],
|
|
||||||
},
|
|
||||||
genai: {
|
|
||||||
fieldOrder: [
|
|
||||||
"provider",
|
|
||||||
"api_key",
|
|
||||||
"base_url",
|
|
||||||
"model",
|
|
||||||
"provider_options",
|
|
||||||
"runtime_options",
|
|
||||||
],
|
|
||||||
advancedFields: ["base_url", "provider_options", "runtime_options"],
|
|
||||||
hiddenFields: ["genai.enabled_in_config"],
|
|
||||||
},
|
|
||||||
classification: {
|
|
||||||
hiddenFields: ["custom"],
|
|
||||||
advancedFields: [],
|
|
||||||
},
|
|
||||||
semantic_search: {
|
|
||||||
fieldOrder: ["enabled", "reindex", "model", "model_size", "device"],
|
|
||||||
advancedFields: ["reindex", "device"],
|
|
||||||
},
|
|
||||||
audio_transcription: {
|
|
||||||
fieldOrder: ["enabled", "language", "device", "model_size", "live_enabled"],
|
|
||||||
advancedFields: ["language", "device", "model_size"],
|
|
||||||
},
|
|
||||||
face_recognition: {
|
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"model_size",
|
|
||||||
"unknown_score",
|
|
||||||
"detection_threshold",
|
|
||||||
"recognition_threshold",
|
|
||||||
"min_area",
|
|
||||||
"min_faces",
|
|
||||||
"save_attempts",
|
|
||||||
"blur_confidence_filter",
|
|
||||||
"device",
|
|
||||||
],
|
|
||||||
advancedFields: [
|
|
||||||
"unknown_score",
|
|
||||||
"detection_threshold",
|
|
||||||
"recognition_threshold",
|
|
||||||
"min_area",
|
|
||||||
"min_faces",
|
|
||||||
"save_attempts",
|
|
||||||
"blur_confidence_filter",
|
|
||||||
"device",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
lpr: {
|
|
||||||
fieldOrder: [
|
|
||||||
"enabled",
|
|
||||||
"model_size",
|
|
||||||
"detection_threshold",
|
|
||||||
"min_area",
|
|
||||||
"recognition_threshold",
|
|
||||||
"min_plate_length",
|
|
||||||
"format",
|
|
||||||
"match_distance",
|
|
||||||
"known_plates",
|
|
||||||
"enhancement",
|
|
||||||
"debug_save_plates",
|
|
||||||
"device",
|
|
||||||
"replace_rules",
|
|
||||||
],
|
|
||||||
advancedFields: [
|
|
||||||
"detection_threshold",
|
|
||||||
"recognition_threshold",
|
|
||||||
"min_plate_length",
|
|
||||||
"format",
|
|
||||||
"match_distance",
|
|
||||||
"known_plates",
|
|
||||||
"enhancement",
|
|
||||||
"debug_save_plates",
|
|
||||||
"device",
|
|
||||||
"replace_rules",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// System sections (global only)
|
// System sections (global only)
|
||||||
const systemSections = [
|
const systemSections = [
|
||||||
"database",
|
{ key: "database", component: DatabaseSection },
|
||||||
"tls",
|
{ key: "tls", component: TlsSection },
|
||||||
"auth",
|
{ key: "auth", component: AuthSection },
|
||||||
"networking",
|
{ key: "networking", component: NetworkingSection },
|
||||||
"proxy",
|
{ key: "proxy", component: ProxySection },
|
||||||
"ui",
|
{ key: "ui", component: UiSection },
|
||||||
"logger",
|
{ key: "logger", component: LoggerSection },
|
||||||
"environment_vars",
|
{ key: "environment_vars", component: EnvironmentVarsSection },
|
||||||
"telemetry",
|
{ key: "telemetry", component: TelemetrySection },
|
||||||
"birdseye",
|
{ key: "birdseye", component: BirdseyeSection },
|
||||||
"ffmpeg",
|
{ key: "ffmpeg", component: FfmpegSection },
|
||||||
"detectors",
|
{ key: "detectors", component: DetectorsSection },
|
||||||
"model",
|
{ key: "model", component: ModelSection },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Integration sections (global only)
|
// Integration sections (global only)
|
||||||
const integrationSections = [
|
const integrationSections = [
|
||||||
"mqtt",
|
{ key: "mqtt", component: MqttSection },
|
||||||
"semantic_search",
|
{ key: "semantic_search", component: SemanticSearchSection },
|
||||||
"genai",
|
{ key: "genai", component: GenaiSection },
|
||||||
"face_recognition",
|
{ key: "face_recognition", component: FaceRecognitionSection },
|
||||||
"lpr",
|
{ key: "lpr", component: LprSection },
|
||||||
"classification",
|
{ key: "classification", component: ClassificationSection },
|
||||||
"audio_transcription",
|
{ key: "audio_transcription", component: AudioTranscriptionSection },
|
||||||
];
|
];
|
||||||
|
|
||||||
interface GlobalConfigSectionProps {
|
|
||||||
sectionKey: string;
|
|
||||||
schema: RJSFSchema | null;
|
|
||||||
config: FrigateConfig | undefined;
|
|
||||||
onSave: () => void;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function GlobalConfigSection({
|
|
||||||
sectionKey,
|
|
||||||
schema,
|
|
||||||
config,
|
|
||||||
onSave,
|
|
||||||
title,
|
|
||||||
}: GlobalConfigSectionProps) {
|
|
||||||
const sectionConfig = globalSectionConfigs[sectionKey];
|
|
||||||
const { t, i18n } = useTranslation([
|
|
||||||
"config/global",
|
|
||||||
"views/settings",
|
|
||||||
"common",
|
|
||||||
]);
|
|
||||||
const [pendingData, setPendingData] = useState<unknown | null>(null);
|
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
|
||||||
const [formKey, setFormKey] = useState(0);
|
|
||||||
const isResettingRef = useRef(false);
|
|
||||||
|
|
||||||
const formData = useMemo((): unknown => {
|
|
||||||
if (!config) return {};
|
|
||||||
return (config as unknown as Record<string, unknown>)[sectionKey];
|
|
||||||
}, [config, sectionKey]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setPendingData(null);
|
|
||||||
}, [formData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isResettingRef.current) {
|
|
||||||
isResettingRef.current = false;
|
|
||||||
}
|
|
||||||
}, [formKey]);
|
|
||||||
|
|
||||||
const hasChanges = useMemo(() => {
|
|
||||||
if (!pendingData) return false;
|
|
||||||
return !isEqual(formData, pendingData);
|
|
||||||
}, [formData, pendingData]);
|
|
||||||
|
|
||||||
const handleChange = useCallback(
|
|
||||||
(data: unknown) => {
|
|
||||||
if (isResettingRef.current) {
|
|
||||||
setPendingData(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!data || typeof data !== "object") {
|
|
||||||
setPendingData(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isEqual(formData, data)) {
|
|
||||||
setPendingData(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setPendingData(data);
|
|
||||||
},
|
|
||||||
[formData],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleReset = useCallback(() => {
|
|
||||||
isResettingRef.current = true;
|
|
||||||
setPendingData(null);
|
|
||||||
setFormKey((prev) => prev + 1);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSave = useCallback(async () => {
|
|
||||||
if (!pendingData) return;
|
|
||||||
|
|
||||||
setIsSaving(true);
|
|
||||||
try {
|
|
||||||
// await axios.put("config/set", {
|
|
||||||
// update_topic: `config/${sectionKey}`,
|
|
||||||
// config_data: {
|
|
||||||
// [sectionKey]: pendingData,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// log axios for debugging
|
|
||||||
console.log("Saved config section", sectionKey, pendingData);
|
|
||||||
|
|
||||||
toast.success(
|
|
||||||
t("toast.success", {
|
|
||||||
ns: "views/settings",
|
|
||||||
defaultValue: "Settings saved successfully",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
setPendingData(null);
|
|
||||||
onSave();
|
|
||||||
} catch {
|
|
||||||
toast.error(
|
|
||||||
t("toast.error", {
|
|
||||||
ns: "views/settings",
|
|
||||||
defaultValue: "Failed to save settings",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setIsSaving(false);
|
|
||||||
}
|
|
||||||
}, [sectionKey, pendingData, t, onSave]);
|
|
||||||
|
|
||||||
if (!schema || !sectionConfig) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Heading as="h4">{title}</Heading>
|
|
||||||
{hasChanges && (
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
{t("modified", { ns: "common", defaultValue: "Modified" })}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<ConfigForm
|
|
||||||
key={formKey}
|
|
||||||
schema={schema}
|
|
||||||
formData={pendingData || formData}
|
|
||||||
onChange={handleChange}
|
|
||||||
fieldOrder={sectionConfig.fieldOrder}
|
|
||||||
hiddenFields={sectionConfig.hiddenFields}
|
|
||||||
advancedFields={sectionConfig.advancedFields}
|
|
||||||
liveValidate={sectionConfig.liveValidate}
|
|
||||||
uiSchema={sectionConfig.uiSchema}
|
|
||||||
showSubmit={false}
|
|
||||||
i18nNamespace="config/global"
|
|
||||||
formContext={{ level: "global", sectionI18nPrefix: sectionKey }}
|
|
||||||
disabled={isSaving}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between pt-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{hasChanges && (
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{t("unsavedChanges", {
|
|
||||||
ns: "views/settings",
|
|
||||||
defaultValue: "You have unsaved changes",
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{hasChanges && (
|
|
||||||
<Button
|
|
||||||
onClick={handleReset}
|
|
||||||
variant="outline"
|
|
||||||
disabled={isSaving}
|
|
||||||
className="gap-2"
|
|
||||||
>
|
|
||||||
{t("reset", { ns: "common", defaultValue: "Reset" })}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
onClick={handleSave}
|
|
||||||
disabled={!hasChanges || isSaving}
|
|
||||||
className="gap-2"
|
|
||||||
>
|
|
||||||
<LuSave className="h-4 w-4" />
|
|
||||||
{isSaving
|
|
||||||
? t("saving", { ns: "common", defaultValue: "Saving..." })
|
|
||||||
: t("save", { ns: "common", defaultValue: "Save" })}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function GlobalConfigView() {
|
export default function GlobalConfigView() {
|
||||||
const { t, i18n } = useTranslation([
|
const { t, i18n } = useTranslation([
|
||||||
"views/settings",
|
"views/settings",
|
||||||
"config/global",
|
"config/global",
|
||||||
"common",
|
"common",
|
||||||
]);
|
]);
|
||||||
|
const defaultSharedSection = sharedSections[0]?.key ?? "";
|
||||||
|
const defaultSystemSection = systemSections[0]?.key ?? "";
|
||||||
|
const defaultIntegrationSection = integrationSections[0]?.key ?? "";
|
||||||
const [activeTab, setActiveTab] = useState("shared");
|
const [activeTab, setActiveTab] = useState("shared");
|
||||||
const [activeSection, setActiveSection] = useState("detect");
|
const [activeSection, setActiveSection] = useState(defaultSharedSection);
|
||||||
|
|
||||||
const { data: config, mutate: refreshConfig } =
|
const { data: config, mutate: refreshConfig } =
|
||||||
useSWR<FrigateConfig>("config");
|
useSWR<FrigateConfig>("config");
|
||||||
const { data: schema } = useSWR<RJSFSchema>("config/schema.json");
|
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
const handleSave = useCallback(() => {
|
||||||
refreshConfig();
|
refreshConfig();
|
||||||
@ -562,32 +104,29 @@ export default function GlobalConfigView() {
|
|||||||
const currentSections = useMemo(() => {
|
const currentSections = useMemo(() => {
|
||||||
if (activeTab === "shared") {
|
if (activeTab === "shared") {
|
||||||
return sharedSections;
|
return sharedSections;
|
||||||
} else if (activeTab === "system") {
|
|
||||||
return systemSections.map((key) => ({
|
|
||||||
key,
|
|
||||||
component: null, // Uses GlobalConfigSection instead
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
return integrationSections.map((key) => ({
|
|
||||||
key,
|
|
||||||
component: null,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
if (activeTab === "system") {
|
||||||
|
return systemSections;
|
||||||
|
}
|
||||||
|
return integrationSections;
|
||||||
}, [activeTab]);
|
}, [activeTab]);
|
||||||
|
|
||||||
// Reset active section when tab changes
|
// Reset active section when tab changes
|
||||||
const handleTabChange = useCallback((tab: string) => {
|
const handleTabChange = useCallback(
|
||||||
setActiveTab(tab);
|
(tab: string) => {
|
||||||
if (tab === "shared") {
|
setActiveTab(tab);
|
||||||
setActiveSection("detect");
|
if (tab === "shared") {
|
||||||
} else if (tab === "system") {
|
setActiveSection(defaultSharedSection);
|
||||||
setActiveSection("database");
|
} else if (tab === "system") {
|
||||||
} else {
|
setActiveSection(defaultSystemSection);
|
||||||
setActiveSection("mqtt");
|
} else {
|
||||||
}
|
setActiveSection(defaultIntegrationSection);
|
||||||
}, []);
|
}
|
||||||
|
},
|
||||||
|
[defaultSharedSection, defaultSystemSection, defaultIntegrationSection],
|
||||||
|
);
|
||||||
|
|
||||||
if (!config || !schema) {
|
if (!config) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center">
|
<div className="flex h-full items-center justify-center">
|
||||||
<ActivityIndicator />
|
<ActivityIndicator />
|
||||||
@ -666,105 +205,42 @@ export default function GlobalConfigView() {
|
|||||||
|
|
||||||
{/* Section Content */}
|
{/* Section Content */}
|
||||||
<div className="scrollbar-container flex-1 overflow-y-auto pr-4">
|
<div className="scrollbar-container flex-1 overflow-y-auto pr-4">
|
||||||
{activeTab === "shared" && (
|
{currentSections.map((section) => {
|
||||||
<>
|
const SectionComponent = section.component;
|
||||||
{sharedSections.map((section) => {
|
return (
|
||||||
const SectionComponent = section.component;
|
<div
|
||||||
return (
|
key={section.key}
|
||||||
<div
|
className={cn(
|
||||||
key={section.key}
|
activeSection === section.key ? "block" : "hidden",
|
||||||
className={cn(
|
)}
|
||||||
activeSection === section.key ? "block" : "hidden",
|
>
|
||||||
)}
|
<Heading as="h4" className="mb-1">
|
||||||
>
|
{t(`${section.key}.label`, {
|
||||||
<Heading as="h4" className="mb-1">
|
ns: "config/global",
|
||||||
{t(`${section.key}.label`, {
|
defaultValue:
|
||||||
ns: "config/global",
|
section.key.charAt(0).toUpperCase() +
|
||||||
defaultValue:
|
section.key.slice(1).replace(/_/g, " "),
|
||||||
section.key.charAt(0).toUpperCase() +
|
})}
|
||||||
section.key.slice(1).replace(/_/g, " "),
|
</Heading>
|
||||||
})}
|
{i18n.exists(`${section.key}.description`, {
|
||||||
</Heading>
|
ns: "config/global",
|
||||||
{i18n.exists(`${section.key}.description`, {
|
}) && (
|
||||||
|
<p className="mb-4 text-sm text-muted-foreground">
|
||||||
|
{t(`${section.key}.description`, {
|
||||||
ns: "config/global",
|
ns: "config/global",
|
||||||
}) && (
|
})}
|
||||||
<p className="mb-4 text-sm text-muted-foreground">
|
</p>
|
||||||
{t(`${section.key}.description`, {
|
)}
|
||||||
ns: "config/global",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<SectionComponent
|
<SectionComponent
|
||||||
level="global"
|
level="global"
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
showTitle={false}
|
showTitle={false}
|
||||||
/>
|
sectionConfig={getSectionConfig(section.key, "global")}
|
||||||
</div>
|
/>
|
||||||
);
|
</div>
|
||||||
})}
|
);
|
||||||
</>
|
})}
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === "system" && (
|
|
||||||
<>
|
|
||||||
{systemSections.map((sectionKey) => {
|
|
||||||
const sectionTitle = t(`${sectionKey}.label`, {
|
|
||||||
ns: "config/global",
|
|
||||||
defaultValue:
|
|
||||||
sectionKey.charAt(0).toUpperCase() +
|
|
||||||
sectionKey.slice(1).replace(/_/g, " "),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={sectionKey}
|
|
||||||
className={cn(
|
|
||||||
activeSection === sectionKey ? "block" : "hidden",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<GlobalConfigSection
|
|
||||||
sectionKey={sectionKey}
|
|
||||||
schema={extractSchemaSection(schema, sectionKey)}
|
|
||||||
config={config}
|
|
||||||
onSave={handleSave}
|
|
||||||
title={sectionTitle}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === "integrations" && (
|
|
||||||
<>
|
|
||||||
{integrationSections.map((sectionKey) => {
|
|
||||||
const sectionTitle = t(`${sectionKey}.label`, {
|
|
||||||
ns: "config/global",
|
|
||||||
defaultValue:
|
|
||||||
sectionKey.charAt(0).toUpperCase() +
|
|
||||||
sectionKey.slice(1).replace(/_/g, " "),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={sectionKey}
|
|
||||||
className={cn(
|
|
||||||
activeSection === sectionKey ? "block" : "hidden",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<GlobalConfigSection
|
|
||||||
sectionKey={sectionKey}
|
|
||||||
schema={extractSchemaSection(schema, sectionKey)}
|
|
||||||
config={config}
|
|
||||||
onSave={handleSave}
|
|
||||||
title={sectionTitle}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
78
web/src/views/settings/SingleSectionPage.tsx
Normal file
78
web/src/views/settings/SingleSectionPage.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Heading from "@/components/ui/heading";
|
||||||
|
import type {
|
||||||
|
BaseSectionProps,
|
||||||
|
SectionConfig,
|
||||||
|
} from "@/components/config-form/sections";
|
||||||
|
import type { PolygonType } from "@/types/canvas";
|
||||||
|
|
||||||
|
export type SettingsPageProps = {
|
||||||
|
selectedCamera?: string;
|
||||||
|
setUnsavedChanges?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
selectedZoneMask?: PolygonType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SingleSectionPageOptions = {
|
||||||
|
sectionKey: string;
|
||||||
|
level: "global" | "camera";
|
||||||
|
SectionComponent: React.ComponentType<BaseSectionProps>;
|
||||||
|
sectionConfig?: SectionConfig;
|
||||||
|
requiresRestart?: boolean;
|
||||||
|
showOverrideIndicator?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createSingleSectionPage({
|
||||||
|
sectionKey,
|
||||||
|
level,
|
||||||
|
SectionComponent,
|
||||||
|
sectionConfig,
|
||||||
|
requiresRestart,
|
||||||
|
showOverrideIndicator = true,
|
||||||
|
}: SingleSectionPageOptions) {
|
||||||
|
return function SingleSectionPage({
|
||||||
|
selectedCamera,
|
||||||
|
setUnsavedChanges,
|
||||||
|
}: SettingsPageProps) {
|
||||||
|
const sectionNamespace =
|
||||||
|
level === "camera" ? "config/cameras" : "config/global";
|
||||||
|
const { t, i18n } = useTranslation([
|
||||||
|
sectionNamespace,
|
||||||
|
"views/settings",
|
||||||
|
"common",
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (level === "camera" && !selectedCamera) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full items-center justify-center text-muted-foreground">
|
||||||
|
{t("configForm.camera.noCameras", { ns: "views/settings" })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex size-full flex-col pr-2">
|
||||||
|
<div className="mb-4">
|
||||||
|
<Heading as="h2">
|
||||||
|
{t(`${sectionKey}.label`, { ns: sectionNamespace })}
|
||||||
|
</Heading>
|
||||||
|
{i18n.exists(`${sectionKey}.description`, {
|
||||||
|
ns: sectionNamespace,
|
||||||
|
}) && (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t(`${sectionKey}.description`, { ns: sectionNamespace })}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<SectionComponent
|
||||||
|
level={level}
|
||||||
|
cameraName={level === "camera" ? selectedCamera : undefined}
|
||||||
|
showOverrideIndicator={showOverrideIndicator}
|
||||||
|
onSave={() => setUnsavedChanges?.(false)}
|
||||||
|
showTitle={false}
|
||||||
|
sectionConfig={sectionConfig}
|
||||||
|
requiresRestart={requiresRestart}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user