mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-18 06:08:22 +03:00
separate and consolidate global and camera i18n namespaces
This commit is contained in:
parent
8f681d5689
commit
3f7f5e3253
@ -26,7 +26,7 @@ class AudioConfig(FrigateBaseModel):
|
|||||||
enabled: bool = Field(
|
enabled: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
title="Enable audio detection",
|
title="Enable audio detection",
|
||||||
description="Enable or disable audio event detection; can be overridden per-camera.",
|
description="Enable or disable audio event detection for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
max_not_heard: int = Field(
|
max_not_heard: int = Field(
|
||||||
default=30,
|
default=30,
|
||||||
|
|||||||
@ -50,7 +50,7 @@ class DetectConfig(FrigateBaseModel):
|
|||||||
enabled: bool = Field(
|
enabled: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
title="Detection enabled",
|
title="Detection enabled",
|
||||||
description="Enable or disable object detection for this camera. Detection must be enabled for object tracking to run.",
|
description="Enable or disable object detection for all cameras; can be overridden per-camera. Detection must be enabled for object tracking to run.",
|
||||||
)
|
)
|
||||||
height: Optional[int] = Field(
|
height: Optional[int] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
|
|||||||
@ -11,7 +11,7 @@ class MotionConfig(FrigateBaseModel):
|
|||||||
enabled: bool = Field(
|
enabled: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
title="Enable motion detection",
|
title="Enable motion detection",
|
||||||
description="Enable or disable motion detection; can be overridden per-camera.",
|
description="Enable or disable motion detection for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
threshold: int = Field(
|
threshold: int = Field(
|
||||||
default=30,
|
default=30,
|
||||||
|
|||||||
@ -11,7 +11,7 @@ class NotificationConfig(FrigateBaseModel):
|
|||||||
enabled: bool = Field(
|
enabled: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
title="Enable notifications",
|
title="Enable notifications",
|
||||||
description="Enable or disable notifications; can be overridden per-camera.",
|
description="Enable or disable notifications for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
email: Optional[str] = Field(
|
email: Optional[str] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
|
|||||||
@ -132,7 +132,7 @@ class ObjectConfig(FrigateBaseModel):
|
|||||||
track: list[str] = Field(
|
track: list[str] = Field(
|
||||||
default=DEFAULT_TRACKED_OBJECTS,
|
default=DEFAULT_TRACKED_OBJECTS,
|
||||||
title="Objects to track",
|
title="Objects to track",
|
||||||
description="List of object labels to track; can be overridden per-camera.",
|
description="List of object labels to track for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
filters: dict[str, FilterConfig] = Field(
|
filters: dict[str, FilterConfig] = Field(
|
||||||
default_factory=dict,
|
default_factory=dict,
|
||||||
|
|||||||
@ -98,7 +98,7 @@ class RecordConfig(FrigateBaseModel):
|
|||||||
enabled: bool = Field(
|
enabled: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
title="Enable recording",
|
title="Enable recording",
|
||||||
description="Enable or disable recording; can be overridden per-camera.",
|
description="Enable or disable recording for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
expire_interval: int = Field(
|
expire_interval: int = Field(
|
||||||
default=60,
|
default=60,
|
||||||
|
|||||||
@ -30,7 +30,7 @@ class SnapshotsConfig(FrigateBaseModel):
|
|||||||
enabled: bool = Field(
|
enabled: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
title="Snapshots enabled",
|
title="Snapshots enabled",
|
||||||
description="Enable or disable saving snapshots; can be overridden per-camera.",
|
description="Enable or disable saving snapshots for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
clean_copy: bool = Field(
|
clean_copy: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
|
|||||||
@ -46,7 +46,7 @@ class AudioTranscriptionConfig(FrigateBaseModel):
|
|||||||
enabled: bool = Field(
|
enabled: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
title="Enable audio transcription",
|
title="Enable audio transcription",
|
||||||
description="Enable or disable automatic audio transcription; can be overridden per-camera.",
|
description="Enable or disable automatic audio transcription for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
language: str = Field(
|
language: str = Field(
|
||||||
default="en",
|
default="en",
|
||||||
@ -240,7 +240,7 @@ class FaceRecognitionConfig(FrigateBaseModel):
|
|||||||
enabled: bool = Field(
|
enabled: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
title="Enable face recognition",
|
title="Enable face recognition",
|
||||||
description="Enable or disable face recognition; can be overridden per-camera.",
|
description="Enable or disable face recognition for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
model_size: str = Field(
|
model_size: str = Field(
|
||||||
default="small",
|
default="small",
|
||||||
@ -322,7 +322,7 @@ class LicensePlateRecognitionConfig(FrigateBaseModel):
|
|||||||
enabled: bool = Field(
|
enabled: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
title="Enable LPR",
|
title="Enable LPR",
|
||||||
description="Enable or disable license plate recognition; can be overridden per-camera.",
|
description="Enable or disable license plate recognition for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
model_size: str = Field(
|
model_size: str = Field(
|
||||||
default="small",
|
default="small",
|
||||||
|
|||||||
@ -346,7 +346,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
notifications: NotificationConfig = Field(
|
notifications: NotificationConfig = Field(
|
||||||
default_factory=NotificationConfig,
|
default_factory=NotificationConfig,
|
||||||
title="Notifications",
|
title="Notifications",
|
||||||
description="Settings to enable and control notifications; can be overridden per-camera.",
|
description="Settings to enable and control notifications for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
networking: NetworkingConfig = Field(
|
networking: NetworkingConfig = Field(
|
||||||
default_factory=NetworkingConfig,
|
default_factory=NetworkingConfig,
|
||||||
@ -398,7 +398,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
audio: AudioConfig = Field(
|
audio: AudioConfig = Field(
|
||||||
default_factory=AudioConfig,
|
default_factory=AudioConfig,
|
||||||
title="Audio events",
|
title="Audio events",
|
||||||
description="Settings for audio-based event detection; can be overridden per-camera.",
|
description="Settings for audio-based event detection for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
birdseye: BirdseyeConfig = Field(
|
birdseye: BirdseyeConfig = Field(
|
||||||
default_factory=BirdseyeConfig,
|
default_factory=BirdseyeConfig,
|
||||||
@ -443,7 +443,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
snapshots: SnapshotsConfig = Field(
|
snapshots: SnapshotsConfig = Field(
|
||||||
default_factory=SnapshotsConfig,
|
default_factory=SnapshotsConfig,
|
||||||
title="Snapshots",
|
title="Snapshots",
|
||||||
description="Settings for saved JPEG snapshots of tracked objects; can be overridden per-camera.",
|
description="Settings for saved JPEG snapshots of tracked objects for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
timestamp_style: TimestampStyleConfig = Field(
|
timestamp_style: TimestampStyleConfig = Field(
|
||||||
default_factory=TimestampStyleConfig,
|
default_factory=TimestampStyleConfig,
|
||||||
@ -470,7 +470,7 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
face_recognition: FaceRecognitionConfig = Field(
|
face_recognition: FaceRecognitionConfig = Field(
|
||||||
default_factory=FaceRecognitionConfig,
|
default_factory=FaceRecognitionConfig,
|
||||||
title="Face recognition",
|
title="Face recognition",
|
||||||
description="Settings for face detection and recognition; can be overridden per-camera.",
|
description="Settings for face detection and recognition for all cameras; can be overridden per-camera.",
|
||||||
)
|
)
|
||||||
lpr: LicensePlateRecognitionConfig = Field(
|
lpr: LicensePlateRecognitionConfig = Field(
|
||||||
default_factory=LicensePlateRecognitionConfig,
|
default_factory=LicensePlateRecognitionConfig,
|
||||||
|
|||||||
@ -209,6 +209,8 @@ def main():
|
|||||||
config_fields = FrigateConfig.model_fields
|
config_fields = FrigateConfig.model_fields
|
||||||
logger.info(f"Found {len(config_fields)} top-level config sections")
|
logger.info(f"Found {len(config_fields)} top-level config sections")
|
||||||
|
|
||||||
|
global_translations = {}
|
||||||
|
|
||||||
for field_name, field_info in config_fields.items():
|
for field_name, field_info in config_fields.items():
|
||||||
if field_name.startswith("_"):
|
if field_name.startswith("_"):
|
||||||
continue
|
continue
|
||||||
@ -351,12 +353,10 @@ def main():
|
|||||||
f"Could not add camera-level fields for {field_name}: {e}"
|
f"Could not add camera-level fields for {field_name}: {e}"
|
||||||
)
|
)
|
||||||
|
|
||||||
output_file = output_dir / f"{field_name}.json"
|
# Add to global translations instead of writing separate files
|
||||||
with open(output_file, "w", encoding="utf-8") as f:
|
global_translations[field_name] = section_data
|
||||||
json.dump(section_data, f, indent=2, ensure_ascii=False)
|
|
||||||
f.write("\n") # Add trailing newline
|
|
||||||
|
|
||||||
logger.info(f"Generated: {output_file}")
|
logger.info(f"Added section to global translations: {field_name}")
|
||||||
|
|
||||||
# Handle camera-level configs that aren't top-level FrigateConfig fields
|
# Handle camera-level configs that aren't top-level FrigateConfig fields
|
||||||
# These are defined as fields in CameraConfig, so we extract title/description from there
|
# These are defined as fields in CameraConfig, so we extract title/description from there
|
||||||
@ -403,15 +403,71 @@ def main():
|
|||||||
}
|
}
|
||||||
section_data.update(nested_without_root)
|
section_data.update(nested_without_root)
|
||||||
|
|
||||||
output_file = output_dir / f"{config_name}.json"
|
# Add camera-level section into global translations (do not write separate file)
|
||||||
with open(output_file, "w", encoding="utf-8") as f:
|
global_translations[config_name] = section_data
|
||||||
json.dump(section_data, f, indent=2, ensure_ascii=False)
|
logger.info(
|
||||||
f.write("\n") # Add trailing newline
|
f"Added camera-level section to global translations: {config_name}"
|
||||||
|
)
|
||||||
logger.info(f"Generated: {output_file}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to generate {config_name}: {e}")
|
logger.error(f"Failed to generate {config_name}: {e}")
|
||||||
|
|
||||||
|
# Remove top-level 'cameras' field if present so it remains a separate file
|
||||||
|
if "cameras" in global_translations:
|
||||||
|
logger.info(
|
||||||
|
"Removing top-level 'cameras' from global translations to keep it as a separate cameras.json"
|
||||||
|
)
|
||||||
|
del global_translations["cameras"]
|
||||||
|
|
||||||
|
# Write consolidated global.json with per-section keys
|
||||||
|
global_file = output_dir / "global.json"
|
||||||
|
with open(global_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(global_translations, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write("\n")
|
||||||
|
|
||||||
|
logger.info(f"Generated consolidated translations: {global_file}")
|
||||||
|
|
||||||
|
if not global_translations:
|
||||||
|
logger.warning("No global translations were generated!")
|
||||||
|
else:
|
||||||
|
logger.info(f"Global contains {len(global_translations)} sections")
|
||||||
|
|
||||||
|
# Generate cameras.json from CameraConfig schema
|
||||||
|
cameras_file = output_dir / "cameras.json"
|
||||||
|
logger.info(f"Generating cameras.json: {cameras_file}")
|
||||||
|
try:
|
||||||
|
if "camera_config_schema" in locals():
|
||||||
|
camera_schema = camera_config_schema
|
||||||
|
else:
|
||||||
|
from frigate.config.camera.camera import CameraConfig
|
||||||
|
|
||||||
|
camera_schema = CameraConfig.model_json_schema()
|
||||||
|
|
||||||
|
camera_translations = extract_translations_from_schema(camera_schema)
|
||||||
|
|
||||||
|
# Change descriptions to use 'for this camera' for fields that are global
|
||||||
|
def sanitize_camera_descriptions(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
for k, v in list(obj.items()):
|
||||||
|
if k == "description" and isinstance(v, str):
|
||||||
|
obj[k] = v.replace(
|
||||||
|
"for all cameras; can be overridden per-camera",
|
||||||
|
"for this camera",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
sanitize_camera_descriptions(v)
|
||||||
|
elif isinstance(obj, list):
|
||||||
|
for item in obj:
|
||||||
|
sanitize_camera_descriptions(item)
|
||||||
|
|
||||||
|
sanitize_camera_descriptions(camera_translations)
|
||||||
|
|
||||||
|
with open(cameras_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(camera_translations, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write("\n")
|
||||||
|
logger.info(f"Generated cameras.json: {cameras_file}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to generate cameras.json: {e}")
|
||||||
|
|
||||||
logger.info("Translation generation complete!")
|
logger.info("Translation generation complete!")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"label": "Cameras",
|
"label": "CameraConfig",
|
||||||
"description": "Cameras",
|
|
||||||
"name": {
|
"name": {
|
||||||
"label": "Camera name",
|
"label": "Camera name",
|
||||||
"description": "Camera name is required"
|
"description": "Camera name is required"
|
||||||
@ -153,7 +152,7 @@
|
|||||||
"description": "FFmpeg settings including binary path, args, hwaccel options, and per-role output args.",
|
"description": "FFmpeg settings including binary path, args, hwaccel options, and per-role output args.",
|
||||||
"path": {
|
"path": {
|
||||||
"label": "FFmpeg path",
|
"label": "FFmpeg path",
|
||||||
"description": "Path to the FFmpeg binary to use for this camera or a version alias (\"5.0\" or \"7.0\")."
|
"description": "Path to the FFmpeg binary to use or a version alias (\"5.0\" or \"7.0\")."
|
||||||
},
|
},
|
||||||
"global_args": {
|
"global_args": {
|
||||||
"label": "FFmpeg global args",
|
"label": "FFmpeg global args",
|
||||||
|
|||||||
1445
web/public/locales/en/config/global.json
Normal file
1445
web/public/locales/en/config/global.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const AudioSection = createConfigSection({
|
export const AudioSection = createConfigSection({
|
||||||
sectionPath: "audio",
|
sectionPath: "audio",
|
||||||
i18nNamespace: "config/audio",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const AudioTranscriptionSection = createConfigSection({
|
export const AudioTranscriptionSection = createConfigSection({
|
||||||
sectionPath: "audio_transcription",
|
sectionPath: "audio_transcription",
|
||||||
i18nNamespace: "config/audio_transcription",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["enabled", "language", "device", "model_size", "live_enabled"],
|
fieldOrder: ["enabled", "language", "device", "model_size", "live_enabled"],
|
||||||
hiddenFields: ["enabled_in_config"],
|
hiddenFields: ["enabled_in_config"],
|
||||||
|
|||||||
@ -460,31 +460,26 @@ export function createConfigSection({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get section title from config namespace. For camera-level sections we
|
// Get section title from config namespace
|
||||||
// prefer the `config/cameras` namespace where keys are nested under the
|
|
||||||
// section name (e.g., `audio.label`). Fall back to provided i18nNamespace.
|
|
||||||
const defaultTitle =
|
const defaultTitle =
|
||||||
sectionPath.charAt(0).toUpperCase() +
|
sectionPath.charAt(0).toUpperCase() +
|
||||||
sectionPath.slice(1).replace(/_/g, " ");
|
sectionPath.slice(1).replace(/_/g, " ");
|
||||||
const title =
|
|
||||||
level === "camera"
|
|
||||||
? t(`${sectionPath}.label`, {
|
|
||||||
ns: "config/cameras",
|
|
||||||
defaultValue: defaultTitle,
|
|
||||||
})
|
|
||||||
: t("label", {
|
|
||||||
ns: i18nNamespace,
|
|
||||||
defaultValue: defaultTitle,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sectionDescription =
|
// For camera-level sections, keys live under `config/cameras` and are
|
||||||
level === "camera"
|
// nested under the section name (e.g., `audio.label`). For global-level
|
||||||
? i18n.exists(`${sectionPath}.description`, { ns: "config/cameras" })
|
// sections, keys are nested under the section name in `config/global`.
|
||||||
? t(`${sectionPath}.description`, { ns: "config/cameras" })
|
const configNamespace =
|
||||||
: undefined
|
level === "camera" ? "config/cameras" : "config/global";
|
||||||
: i18n.exists("description", { ns: i18nNamespace })
|
const title = t(`${sectionPath}.label`, {
|
||||||
? t("description", { ns: i18nNamespace })
|
ns: configNamespace,
|
||||||
: undefined;
|
defaultValue: defaultTitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sectionDescription = i18n.exists(`${sectionPath}.description`, {
|
||||||
|
ns: configNamespace,
|
||||||
|
})
|
||||||
|
? t(`${sectionPath}.description`, { ns: configNamespace })
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const sectionContent = (
|
const sectionContent = (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@ -502,7 +497,7 @@ export function createConfigSection({
|
|||||||
disabled={disabled || isSaving}
|
disabled={disabled || isSaving}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
showSubmit={false}
|
showSubmit={false}
|
||||||
i18nNamespace={i18nNamespace}
|
i18nNamespace={configNamespace}
|
||||||
formContext={{
|
formContext={{
|
||||||
level,
|
level,
|
||||||
cameraName,
|
cameraName,
|
||||||
@ -516,7 +511,10 @@ export function createConfigSection({
|
|||||||
fullConfig: config,
|
fullConfig: config,
|
||||||
// When rendering camera-level sections, provide the section path so
|
// When rendering camera-level sections, provide the section path so
|
||||||
// field templates can look up keys under the `config/cameras` namespace
|
// field templates can look up keys under the `config/cameras` namespace
|
||||||
sectionI18nPrefix: level === "camera" ? sectionPath : undefined,
|
// When using a consolidated global namespace, keys are nested
|
||||||
|
// under the section name (e.g., `audio.label`) so provide the
|
||||||
|
// section prefix to templates so they can attempt `${section}.${field}` lookups.
|
||||||
|
sectionI18nPrefix: sectionPath,
|
||||||
t,
|
t,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const BirdseyeSection = createConfigSection({
|
export const BirdseyeSection = createConfigSection({
|
||||||
sectionPath: "birdseye",
|
sectionPath: "birdseye",
|
||||||
i18nNamespace: "config/birdseye",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["enabled", "mode", "order"],
|
fieldOrder: ["enabled", "mode", "order"],
|
||||||
hiddenFields: [],
|
hiddenFields: [],
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const CameraMqttSection = createConfigSection({
|
export const CameraMqttSection = createConfigSection({
|
||||||
sectionPath: "mqtt",
|
sectionPath: "mqtt",
|
||||||
i18nNamespace: "config/camera_mqtt",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const CameraUiSection = createConfigSection({
|
export const CameraUiSection = createConfigSection({
|
||||||
sectionPath: "ui",
|
sectionPath: "ui",
|
||||||
i18nNamespace: "config/camera_ui",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["dashboard", "order"],
|
fieldOrder: ["dashboard", "order"],
|
||||||
hiddenFields: [],
|
hiddenFields: [],
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const DetectSection = createConfigSection({
|
export const DetectSection = createConfigSection({
|
||||||
sectionPath: "detect",
|
sectionPath: "detect",
|
||||||
i18nNamespace: "config/detect",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const FaceRecognitionSection = createConfigSection({
|
export const FaceRecognitionSection = createConfigSection({
|
||||||
sectionPath: "face_recognition",
|
sectionPath: "face_recognition",
|
||||||
i18nNamespace: "config/face_recognition",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["enabled", "min_area"],
|
fieldOrder: ["enabled", "min_area"],
|
||||||
hiddenFields: [],
|
hiddenFields: [],
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const FfmpegSection = createConfigSection({
|
export const FfmpegSection = createConfigSection({
|
||||||
sectionPath: "ffmpeg",
|
sectionPath: "ffmpeg",
|
||||||
i18nNamespace: "config/ffmpeg",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"inputs",
|
"inputs",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const LiveSection = createConfigSection({
|
export const LiveSection = createConfigSection({
|
||||||
sectionPath: "live",
|
sectionPath: "live",
|
||||||
i18nNamespace: "config/live",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["stream_name", "height", "quality"],
|
fieldOrder: ["stream_name", "height", "quality"],
|
||||||
fieldGroups: {},
|
fieldGroups: {},
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const LprSection = createConfigSection({
|
export const LprSection = createConfigSection({
|
||||||
sectionPath: "lpr",
|
sectionPath: "lpr",
|
||||||
i18nNamespace: "config/lpr",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["enabled", "expire_time", "min_area", "enhancement"],
|
fieldOrder: ["enabled", "expire_time", "min_area", "enhancement"],
|
||||||
hiddenFields: [],
|
hiddenFields: [],
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const MotionSection = createConfigSection({
|
export const MotionSection = createConfigSection({
|
||||||
sectionPath: "motion",
|
sectionPath: "motion",
|
||||||
i18nNamespace: "config/motion",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const NotificationsSection = createConfigSection({
|
export const NotificationsSection = createConfigSection({
|
||||||
sectionPath: "notifications",
|
sectionPath: "notifications",
|
||||||
i18nNamespace: "config/notifications",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["enabled", "email"],
|
fieldOrder: ["enabled", "email"],
|
||||||
fieldGroups: {},
|
fieldGroups: {},
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const ObjectsSection = createConfigSection({
|
export const ObjectsSection = createConfigSection({
|
||||||
sectionPath: "objects",
|
sectionPath: "objects",
|
||||||
i18nNamespace: "config/objects",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["track", "alert", "detect", "filters"],
|
fieldOrder: ["track", "alert", "detect", "filters"],
|
||||||
fieldGroups: {
|
fieldGroups: {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const OnvifSection = createConfigSection({
|
export const OnvifSection = createConfigSection({
|
||||||
sectionPath: "onvif",
|
sectionPath: "onvif",
|
||||||
i18nNamespace: "config/onvif",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"host",
|
"host",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const RecordSection = createConfigSection({
|
export const RecordSection = createConfigSection({
|
||||||
sectionPath: "record",
|
sectionPath: "record",
|
||||||
i18nNamespace: "config/record",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const ReviewSection = createConfigSection({
|
export const ReviewSection = createConfigSection({
|
||||||
sectionPath: "review",
|
sectionPath: "review",
|
||||||
i18nNamespace: "config/review",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["alerts", "detections", "genai"],
|
fieldOrder: ["alerts", "detections", "genai"],
|
||||||
fieldGroups: {},
|
fieldGroups: {},
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const SemanticSearchSection = createConfigSection({
|
export const SemanticSearchSection = createConfigSection({
|
||||||
sectionPath: "semantic_search",
|
sectionPath: "semantic_search",
|
||||||
i18nNamespace: "config/semantic_search",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["triggers"],
|
fieldOrder: ["triggers"],
|
||||||
hiddenFields: [],
|
hiddenFields: [],
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const SnapshotsSection = createConfigSection({
|
export const SnapshotsSection = createConfigSection({
|
||||||
sectionPath: "snapshots",
|
sectionPath: "snapshots",
|
||||||
i18nNamespace: "config/snapshots",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
|||||||
|
|
||||||
export const TimestampSection = createConfigSection({
|
export const TimestampSection = createConfigSection({
|
||||||
sectionPath: "timestamp_style",
|
sectionPath: "timestamp_style",
|
||||||
i18nNamespace: "config/timestamp_style",
|
i18nNamespace: "config/global",
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
fieldOrder: ["position", "format", "color", "thickness"],
|
fieldOrder: ["position", "format", "color", "thickness"],
|
||||||
hiddenFields: ["effect", "enabled_in_config"],
|
hiddenFields: ["effect", "enabled_in_config"],
|
||||||
|
|||||||
@ -21,7 +21,8 @@ export function DescriptionFieldTemplate(props: DescriptionFieldProps) {
|
|||||||
|
|
||||||
let resolvedDescription = description;
|
let resolvedDescription = description;
|
||||||
|
|
||||||
if (isCameraLevel && sectionI18nPrefix && effectiveNamespace) {
|
// Support nested keys for both camera-level and consolidated global namespace
|
||||||
|
if (sectionI18nPrefix && effectiveNamespace) {
|
||||||
const descriptionKey = `${sectionI18nPrefix}.description`;
|
const descriptionKey = `${sectionI18nPrefix}.description`;
|
||||||
if (i18n.exists(descriptionKey, { ns: effectiveNamespace })) {
|
if (i18n.exists(descriptionKey, { ns: effectiveNamespace })) {
|
||||||
resolvedDescription = t(descriptionKey, { ns: effectiveNamespace });
|
resolvedDescription = t(descriptionKey, { ns: effectiveNamespace });
|
||||||
|
|||||||
@ -199,7 +199,7 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
|||||||
|
|
||||||
const label = domain
|
const label = domain
|
||||||
? t(`${domain}.${groupKey}`, {
|
? t(`${domain}.${groupKey}`, {
|
||||||
ns: "config/groups",
|
ns: "config/global",
|
||||||
defaultValue: toTitle(groupKey),
|
defaultValue: toTitle(groupKey),
|
||||||
})
|
})
|
||||||
: t(`groups.${groupKey}`, {
|
: t(`groups.${groupKey}`, {
|
||||||
|
|||||||
@ -52,27 +52,10 @@ i18n
|
|||||||
"views/system",
|
"views/system",
|
||||||
"views/exports",
|
"views/exports",
|
||||||
"views/explore",
|
"views/explore",
|
||||||
// Config section translations
|
// Config namespaces: single consolidated global file + camera-level keys
|
||||||
|
"config/global",
|
||||||
"config/cameras",
|
"config/cameras",
|
||||||
"config/detect",
|
// keep these for backwards compatibility with explicit ns usage
|
||||||
"config/record",
|
|
||||||
"config/snapshots",
|
|
||||||
"config/motion",
|
|
||||||
"config/objects",
|
|
||||||
"config/review",
|
|
||||||
"config/audio",
|
|
||||||
"config/notifications",
|
|
||||||
"config/live",
|
|
||||||
"config/timestamp_style",
|
|
||||||
"config/mqtt",
|
|
||||||
"config/database",
|
|
||||||
"config/auth",
|
|
||||||
"config/tls",
|
|
||||||
"config/telemetry",
|
|
||||||
"config/birdseye",
|
|
||||||
"config/semantic_search",
|
|
||||||
"config/face_recognition",
|
|
||||||
"config/lpr",
|
|
||||||
"config/validation",
|
"config/validation",
|
||||||
"config/groups",
|
"config/groups",
|
||||||
],
|
],
|
||||||
|
|||||||
@ -180,29 +180,12 @@ const CameraConfigContent = memo(function CameraConfigContent({
|
|||||||
onSave,
|
onSave,
|
||||||
}: CameraConfigContentProps) {
|
}: CameraConfigContentProps) {
|
||||||
const { t } = useTranslation([
|
const { t } = useTranslation([
|
||||||
"config/detect",
|
|
||||||
"config/record",
|
|
||||||
"config/snapshots",
|
|
||||||
"config/motion",
|
|
||||||
"config/objects",
|
|
||||||
"config/review",
|
|
||||||
"config/audio",
|
|
||||||
"config/cameras",
|
"config/cameras",
|
||||||
"config/audio_transcription",
|
"config/cameras",
|
||||||
"config/birdseye",
|
|
||||||
"config/camera_mqtt",
|
|
||||||
"config/camera_ui",
|
|
||||||
"config/face_recognition",
|
|
||||||
"config/ffmpeg",
|
|
||||||
"config/lpr",
|
|
||||||
"config/notifications",
|
|
||||||
"config/onvif",
|
|
||||||
"config/live",
|
|
||||||
"config/semantic_search",
|
|
||||||
"config/timestamp_style",
|
|
||||||
"views/settings",
|
"views/settings",
|
||||||
"common",
|
"common",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [activeSection, setActiveSection] = useState("detect");
|
const [activeSection, setActiveSection] = useState("detect");
|
||||||
|
|
||||||
const cameraConfig = config.cameras?.[cameraName];
|
const cameraConfig = config.cameras?.[cameraName];
|
||||||
@ -226,92 +209,92 @@ const CameraConfigContent = memo(function CameraConfigContent({
|
|||||||
}> = [
|
}> = [
|
||||||
{
|
{
|
||||||
key: "detect",
|
key: "detect",
|
||||||
i18nNamespace: "config/detect",
|
i18nNamespace: "config/cameras",
|
||||||
component: DetectSection,
|
component: DetectSection,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "ffmpeg",
|
key: "ffmpeg",
|
||||||
i18nNamespace: "config/ffmpeg",
|
i18nNamespace: "config/cameras",
|
||||||
component: FfmpegSection,
|
component: FfmpegSection,
|
||||||
showOverrideIndicator: true,
|
showOverrideIndicator: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "record",
|
key: "record",
|
||||||
i18nNamespace: "config/record",
|
i18nNamespace: "config/cameras",
|
||||||
component: RecordSection,
|
component: RecordSection,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "snapshots",
|
key: "snapshots",
|
||||||
i18nNamespace: "config/snapshots",
|
i18nNamespace: "config/cameras",
|
||||||
component: SnapshotsSection,
|
component: SnapshotsSection,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "motion",
|
key: "motion",
|
||||||
i18nNamespace: "config/motion",
|
i18nNamespace: "config/cameras",
|
||||||
component: MotionSection,
|
component: MotionSection,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "objects",
|
key: "objects",
|
||||||
i18nNamespace: "config/objects",
|
i18nNamespace: "config/cameras",
|
||||||
component: ObjectsSection,
|
component: ObjectsSection,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "review",
|
key: "review",
|
||||||
i18nNamespace: "config/review",
|
i18nNamespace: "config/cameras",
|
||||||
component: ReviewSection,
|
component: ReviewSection,
|
||||||
},
|
},
|
||||||
{ key: "audio", i18nNamespace: "config/audio", component: AudioSection },
|
{ key: "audio", i18nNamespace: "config/cameras", component: AudioSection },
|
||||||
{
|
{
|
||||||
key: "audio_transcription",
|
key: "audio_transcription",
|
||||||
i18nNamespace: "config/audio_transcription",
|
i18nNamespace: "config/cameras",
|
||||||
component: AudioTranscriptionSection,
|
component: AudioTranscriptionSection,
|
||||||
showOverrideIndicator: true,
|
showOverrideIndicator: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "notifications",
|
key: "notifications",
|
||||||
i18nNamespace: "config/notifications",
|
i18nNamespace: "config/cameras",
|
||||||
component: NotificationsSection,
|
component: NotificationsSection,
|
||||||
},
|
},
|
||||||
{ key: "live", i18nNamespace: "config/live", component: LiveSection },
|
{ key: "live", i18nNamespace: "config/cameras", component: LiveSection },
|
||||||
{
|
{
|
||||||
key: "birdseye",
|
key: "birdseye",
|
||||||
i18nNamespace: "config/birdseye",
|
i18nNamespace: "config/cameras",
|
||||||
component: BirdseyeSection,
|
component: BirdseyeSection,
|
||||||
showOverrideIndicator: true,
|
showOverrideIndicator: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "face_recognition",
|
key: "face_recognition",
|
||||||
i18nNamespace: "config/face_recognition",
|
i18nNamespace: "config/cameras",
|
||||||
component: FaceRecognitionSection,
|
component: FaceRecognitionSection,
|
||||||
showOverrideIndicator: true,
|
showOverrideIndicator: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "lpr",
|
key: "lpr",
|
||||||
i18nNamespace: "config/lpr",
|
i18nNamespace: "config/cameras",
|
||||||
component: LprSection,
|
component: LprSection,
|
||||||
showOverrideIndicator: true,
|
showOverrideIndicator: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "mqtt",
|
key: "mqtt",
|
||||||
i18nNamespace: "config/camera_mqtt",
|
i18nNamespace: "config/cameras",
|
||||||
component: CameraMqttSection,
|
component: CameraMqttSection,
|
||||||
showOverrideIndicator: false,
|
showOverrideIndicator: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "onvif",
|
key: "onvif",
|
||||||
i18nNamespace: "config/onvif",
|
i18nNamespace: "config/cameras",
|
||||||
component: OnvifSection,
|
component: OnvifSection,
|
||||||
showOverrideIndicator: false,
|
showOverrideIndicator: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "ui",
|
key: "ui",
|
||||||
i18nNamespace: "config/camera_ui",
|
i18nNamespace: "config/cameras",
|
||||||
component: CameraUiSection,
|
component: CameraUiSection,
|
||||||
showOverrideIndicator: false,
|
showOverrideIndicator: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "timestamp_style",
|
key: "timestamp_style",
|
||||||
i18nNamespace: "config/timestamp_style",
|
i18nNamespace: "config/cameras",
|
||||||
component: TimestampSection,
|
component: TimestampSection,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -326,12 +309,10 @@ const CameraConfigContent = memo(function CameraConfigContent({
|
|||||||
const defaultSectionLabel =
|
const defaultSectionLabel =
|
||||||
section.key.charAt(0).toUpperCase() +
|
section.key.charAt(0).toUpperCase() +
|
||||||
section.key.slice(1).replace(/_/g, " ");
|
section.key.slice(1).replace(/_/g, " ");
|
||||||
|
|
||||||
const sectionLabel = t(`${section.key}.label`, {
|
const sectionLabel = t(`${section.key}.label`, {
|
||||||
ns: "config/cameras",
|
ns: "config/cameras",
|
||||||
defaultValue: t("label", {
|
defaultValue: defaultSectionLabel,
|
||||||
ns: section.i18nNamespace,
|
|
||||||
defaultValue: defaultSectionLabel,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -30,25 +30,25 @@ import { cn } from "@/lib/utils";
|
|||||||
|
|
||||||
// Shared sections that can be overridden at camera level
|
// Shared sections that can be overridden at camera level
|
||||||
const sharedSections = [
|
const sharedSections = [
|
||||||
{ key: "detect", i18nNamespace: "config/detect", component: DetectSection },
|
{ key: "detect", i18nNamespace: "config/global", component: DetectSection },
|
||||||
{ key: "record", i18nNamespace: "config/record", component: RecordSection },
|
{ key: "record", i18nNamespace: "config/global", component: RecordSection },
|
||||||
{
|
{
|
||||||
key: "snapshots",
|
key: "snapshots",
|
||||||
i18nNamespace: "config/snapshots",
|
i18nNamespace: "config/global",
|
||||||
component: SnapshotsSection,
|
component: SnapshotsSection,
|
||||||
},
|
},
|
||||||
{ key: "motion", i18nNamespace: "config/motion", component: MotionSection },
|
{ key: "motion", i18nNamespace: "config/global", component: MotionSection },
|
||||||
{
|
{
|
||||||
key: "objects",
|
key: "objects",
|
||||||
i18nNamespace: "config/objects",
|
i18nNamespace: "config/global",
|
||||||
component: ObjectsSection,
|
component: ObjectsSection,
|
||||||
},
|
},
|
||||||
{ key: "review", i18nNamespace: "config/review", component: ReviewSection },
|
{ key: "review", i18nNamespace: "config/global", component: ReviewSection },
|
||||||
{ key: "audio", i18nNamespace: "config/audio", component: AudioSection },
|
{ key: "audio", i18nNamespace: "config/global", component: AudioSection },
|
||||||
{ key: "live", i18nNamespace: "config/live", component: LiveSection },
|
{ key: "live", i18nNamespace: "config/global", component: LiveSection },
|
||||||
{
|
{
|
||||||
key: "timestamp_style",
|
key: "timestamp_style",
|
||||||
i18nNamespace: "config/timestamp_style",
|
i18nNamespace: "config/global",
|
||||||
component: TimestampSection,
|
component: TimestampSection,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -66,7 +66,7 @@ const globalSectionConfigs: Record<
|
|||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
mqtt: {
|
mqtt: {
|
||||||
i18nNamespace: "config/mqtt",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
"host",
|
"host",
|
||||||
@ -93,12 +93,12 @@ const globalSectionConfigs: Record<
|
|||||||
liveValidate: true,
|
liveValidate: true,
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
i18nNamespace: "config/database",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: ["path"],
|
fieldOrder: ["path"],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
i18nNamespace: "config/auth",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
"reset_admin_password",
|
"reset_admin_password",
|
||||||
@ -130,17 +130,17 @@ const globalSectionConfigs: Record<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
tls: {
|
tls: {
|
||||||
i18nNamespace: "config/tls",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: ["enabled", "cert", "key"],
|
fieldOrder: ["enabled", "cert", "key"],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
},
|
},
|
||||||
networking: {
|
networking: {
|
||||||
i18nNamespace: "config/networking",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: ["ipv6"],
|
fieldOrder: ["ipv6"],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
},
|
},
|
||||||
proxy: {
|
proxy: {
|
||||||
i18nNamespace: "config/proxy",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"header_map",
|
"header_map",
|
||||||
"logout_url",
|
"logout_url",
|
||||||
@ -152,7 +152,7 @@ const globalSectionConfigs: Record<
|
|||||||
liveValidate: true,
|
liveValidate: true,
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
i18nNamespace: "config/ui",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"timezone",
|
"timezone",
|
||||||
"time_format",
|
"time_format",
|
||||||
@ -163,22 +163,22 @@ const globalSectionConfigs: Record<
|
|||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
},
|
},
|
||||||
logger: {
|
logger: {
|
||||||
i18nNamespace: "config/logger",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: ["default", "logs"],
|
fieldOrder: ["default", "logs"],
|
||||||
advancedFields: ["logs"],
|
advancedFields: ["logs"],
|
||||||
},
|
},
|
||||||
environment_vars: {
|
environment_vars: {
|
||||||
i18nNamespace: "config/environment_vars",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [],
|
fieldOrder: [],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
},
|
},
|
||||||
telemetry: {
|
telemetry: {
|
||||||
i18nNamespace: "config/telemetry",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: ["network_interfaces", "stats", "version_check"],
|
fieldOrder: ["network_interfaces", "stats", "version_check"],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
},
|
},
|
||||||
birdseye: {
|
birdseye: {
|
||||||
i18nNamespace: "config/birdseye",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
"restream",
|
"restream",
|
||||||
@ -193,7 +193,7 @@ const globalSectionConfigs: Record<
|
|||||||
advancedFields: ["width", "height", "quality", "inactivity_threshold"],
|
advancedFields: ["width", "height", "quality", "inactivity_threshold"],
|
||||||
},
|
},
|
||||||
ffmpeg: {
|
ffmpeg: {
|
||||||
i18nNamespace: "config/ffmpeg",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"path",
|
"path",
|
||||||
"global_args",
|
"global_args",
|
||||||
@ -253,12 +253,12 @@ const globalSectionConfigs: Record<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
detectors: {
|
detectors: {
|
||||||
i18nNamespace: "config/detectors",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [],
|
fieldOrder: [],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
i18nNamespace: "config/model",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"path",
|
"path",
|
||||||
"labelmap_path",
|
"labelmap_path",
|
||||||
@ -278,7 +278,7 @@ const globalSectionConfigs: Record<
|
|||||||
hiddenFields: ["labelmap", "attributes_map"],
|
hiddenFields: ["labelmap", "attributes_map"],
|
||||||
},
|
},
|
||||||
genai: {
|
genai: {
|
||||||
i18nNamespace: "config/genai",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"provider",
|
"provider",
|
||||||
"api_key",
|
"api_key",
|
||||||
@ -291,22 +291,22 @@ const globalSectionConfigs: Record<
|
|||||||
hiddenFields: ["genai.enabled_in_config"],
|
hiddenFields: ["genai.enabled_in_config"],
|
||||||
},
|
},
|
||||||
classification: {
|
classification: {
|
||||||
i18nNamespace: "config/classification",
|
i18nNamespace: "config/global",
|
||||||
hiddenFields: ["custom"],
|
hiddenFields: ["custom"],
|
||||||
advancedFields: [],
|
advancedFields: [],
|
||||||
},
|
},
|
||||||
semantic_search: {
|
semantic_search: {
|
||||||
i18nNamespace: "config/semantic_search",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: ["enabled", "reindex", "model", "model_size", "device"],
|
fieldOrder: ["enabled", "reindex", "model", "model_size", "device"],
|
||||||
advancedFields: ["reindex", "device"],
|
advancedFields: ["reindex", "device"],
|
||||||
},
|
},
|
||||||
audio_transcription: {
|
audio_transcription: {
|
||||||
i18nNamespace: "config/audio_transcription",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: ["enabled", "language", "device", "model_size", "live_enabled"],
|
fieldOrder: ["enabled", "language", "device", "model_size", "live_enabled"],
|
||||||
advancedFields: ["language", "device", "model_size"],
|
advancedFields: ["language", "device", "model_size"],
|
||||||
},
|
},
|
||||||
face_recognition: {
|
face_recognition: {
|
||||||
i18nNamespace: "config/face_recognition",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
"model_size",
|
"model_size",
|
||||||
@ -331,7 +331,7 @@ const globalSectionConfigs: Record<
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
lpr: {
|
lpr: {
|
||||||
i18nNamespace: "config/lpr",
|
i18nNamespace: "config/global",
|
||||||
fieldOrder: [
|
fieldOrder: [
|
||||||
"enabled",
|
"enabled",
|
||||||
"model_size",
|
"model_size",
|
||||||
@ -522,7 +522,8 @@ function GlobalConfigSection({
|
|||||||
liveValidate={sectionConfig.liveValidate}
|
liveValidate={sectionConfig.liveValidate}
|
||||||
uiSchema={sectionConfig.uiSchema}
|
uiSchema={sectionConfig.uiSchema}
|
||||||
showSubmit={false}
|
showSubmit={false}
|
||||||
i18nNamespace={sectionConfig.i18nNamespace}
|
formContext={{ sectionI18nPrefix: sectionKey }}
|
||||||
|
i18nNamespace="config/global"
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -565,44 +566,7 @@ function GlobalConfigSection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function GlobalConfigView() {
|
export default function GlobalConfigView() {
|
||||||
const { t } = useTranslation([
|
const { t } = useTranslation(["views/settings", "config/global", "common"]);
|
||||||
"views/settings",
|
|
||||||
"config/detect",
|
|
||||||
"config/record",
|
|
||||||
"config/snapshots",
|
|
||||||
"config/motion",
|
|
||||||
"config/objects",
|
|
||||||
"config/review",
|
|
||||||
"config/audio",
|
|
||||||
"config/notifications",
|
|
||||||
"config/live",
|
|
||||||
"config/timestamp_style",
|
|
||||||
"config/mqtt",
|
|
||||||
"config/audio_transcription",
|
|
||||||
"config/database",
|
|
||||||
"config/auth",
|
|
||||||
"config/tls",
|
|
||||||
"config/networking",
|
|
||||||
"config/proxy",
|
|
||||||
"config/ui",
|
|
||||||
"config/logger",
|
|
||||||
"config/environment_vars",
|
|
||||||
"config/telemetry",
|
|
||||||
"config/birdseye",
|
|
||||||
"config/ffmpeg",
|
|
||||||
"config/detectors",
|
|
||||||
"config/model",
|
|
||||||
"config/genai",
|
|
||||||
"config/classification",
|
|
||||||
"config/semantic_search",
|
|
||||||
"config/face_recognition",
|
|
||||||
"config/lpr",
|
|
||||||
"config/go2rtc",
|
|
||||||
"config/camera_groups",
|
|
||||||
"config/safe_mode",
|
|
||||||
"config/version",
|
|
||||||
"common",
|
|
||||||
]);
|
|
||||||
const [activeTab, setActiveTab] = useState("shared");
|
const [activeTab, setActiveTab] = useState("shared");
|
||||||
const [activeSection, setActiveSection] = useState("detect");
|
const [activeSection, setActiveSection] = useState("detect");
|
||||||
|
|
||||||
@ -695,11 +659,12 @@ export default function GlobalConfigView() {
|
|||||||
<nav className="w-64 shrink-0">
|
<nav className="w-64 shrink-0">
|
||||||
<ul className="space-y-1">
|
<ul className="space-y-1">
|
||||||
{currentSections.map((section) => {
|
{currentSections.map((section) => {
|
||||||
const sectionLabel = t("label", {
|
const defaultLabel =
|
||||||
ns: section.i18nNamespace,
|
section.key.charAt(0).toUpperCase() +
|
||||||
defaultValue:
|
section.key.slice(1).replace(/_/g, " ");
|
||||||
section.key.charAt(0).toUpperCase() +
|
const sectionLabel = t(`${section.key}.label`, {
|
||||||
section.key.slice(1).replace(/_/g, " "),
|
ns: "config/global",
|
||||||
|
defaultValue: defaultLabel,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -735,8 +700,8 @@ export default function GlobalConfigView() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Heading as="h4" className="mb-4">
|
<Heading as="h4" className="mb-4">
|
||||||
{t("label", {
|
{t(`${section.key}.label`, {
|
||||||
ns: section.i18nNamespace,
|
ns: "config/global",
|
||||||
defaultValue:
|
defaultValue:
|
||||||
section.key.charAt(0).toUpperCase() +
|
section.key.charAt(0).toUpperCase() +
|
||||||
section.key.slice(1).replace(/_/g, " "),
|
section.key.slice(1).replace(/_/g, " "),
|
||||||
@ -756,12 +721,21 @@ export default function GlobalConfigView() {
|
|||||||
{activeTab === "system" && (
|
{activeTab === "system" && (
|
||||||
<>
|
<>
|
||||||
{systemSections.map((sectionKey) => {
|
{systemSections.map((sectionKey) => {
|
||||||
const sectionTitle = t("label", {
|
const ns = globalSectionConfigs[sectionKey].i18nNamespace;
|
||||||
ns: globalSectionConfigs[sectionKey].i18nNamespace,
|
const sectionTitle =
|
||||||
defaultValue:
|
ns === "config/global"
|
||||||
sectionKey.charAt(0).toUpperCase() +
|
? t(`${sectionKey}.label`, {
|
||||||
sectionKey.slice(1).replace(/_/g, " "),
|
ns: "config/global",
|
||||||
});
|
defaultValue:
|
||||||
|
sectionKey.charAt(0).toUpperCase() +
|
||||||
|
sectionKey.slice(1).replace(/_/g, " "),
|
||||||
|
})
|
||||||
|
: t("label", {
|
||||||
|
ns,
|
||||||
|
defaultValue:
|
||||||
|
sectionKey.charAt(0).toUpperCase() +
|
||||||
|
sectionKey.slice(1).replace(/_/g, " "),
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -786,12 +760,21 @@ export default function GlobalConfigView() {
|
|||||||
{activeTab === "integrations" && (
|
{activeTab === "integrations" && (
|
||||||
<>
|
<>
|
||||||
{integrationSections.map((sectionKey) => {
|
{integrationSections.map((sectionKey) => {
|
||||||
const sectionTitle = t("label", {
|
const ns = globalSectionConfigs[sectionKey].i18nNamespace;
|
||||||
ns: globalSectionConfigs[sectionKey].i18nNamespace,
|
const sectionTitle =
|
||||||
defaultValue:
|
ns === "config/global"
|
||||||
sectionKey.charAt(0).toUpperCase() +
|
? t(`${sectionKey}.label`, {
|
||||||
sectionKey.slice(1).replace(/_/g, " "),
|
ns: "config/global",
|
||||||
});
|
defaultValue:
|
||||||
|
sectionKey.charAt(0).toUpperCase() +
|
||||||
|
sectionKey.slice(1).replace(/_/g, " "),
|
||||||
|
})
|
||||||
|
: t("label", {
|
||||||
|
ns,
|
||||||
|
defaultValue:
|
||||||
|
sectionKey.charAt(0).toUpperCase() +
|
||||||
|
sectionKey.slice(1).replace(/_/g, " "),
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user