add global sections, camera config overrides, and reset button

This commit is contained in:
Josh Hawkins
2026-02-27 09:37:57 -06:00
parent 260237bf1a
commit 7692c89234
15 changed files with 682 additions and 60 deletions
+81 -4
View File
@@ -11,8 +11,17 @@ import { MotionSection } from "@/components/config-form/sections/MotionSection";
import { ObjectsSection } from "@/components/config-form/sections/ObjectsSection";
import { ReviewSection } from "@/components/config-form/sections/ReviewSection";
import { AudioSection } from "@/components/config-form/sections/AudioSection";
import { AudioTranscriptionSection } from "@/components/config-form/sections/AudioTranscriptionSection";
import { BirdseyeSection } from "@/components/config-form/sections/BirdseyeSection";
import { CameraMqttSection } from "@/components/config-form/sections/CameraMqttSection";
import { CameraUiSection } from "@/components/config-form/sections/CameraUiSection";
import { FaceRecognitionSection } from "@/components/config-form/sections/FaceRecognitionSection";
import { FfmpegSection } from "@/components/config-form/sections/FfmpegSection";
import { LprSection } from "@/components/config-form/sections/LprSection";
import { NotificationsSection } from "@/components/config-form/sections/NotificationsSection";
import { OnvifSection } from "@/components/config-form/sections/OnvifSection";
import { LiveSection } from "@/components/config-form/sections/LiveSection";
import { SemanticSearchSection } from "@/components/config-form/sections/SemanticSearchSection";
import { TimestampSection } from "@/components/config-form/sections/TimestampSection";
import { useAllCameraOverrides } from "@/hooks/use-config-override";
import type { FrigateConfig } from "@/types/frigateConfig";
@@ -179,8 +188,17 @@ const CameraConfigContent = memo(function CameraConfigContent({
"config/objects",
"config/review",
"config/audio",
"config/audio_transcription",
"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",
"common",
@@ -200,12 +218,23 @@ const CameraConfigContent = memo(function CameraConfigContent({
);
}
const sections = [
const sections: Array<{
key: string;
i18nNamespace: string;
component: typeof DetectSection;
showOverrideIndicator?: boolean;
}> = [
{
key: "detect",
i18nNamespace: "config/detect",
component: DetectSection,
},
{
key: "ffmpeg",
i18nNamespace: "config/ffmpeg",
component: FfmpegSection,
showOverrideIndicator: true,
},
{
key: "record",
i18nNamespace: "config/record",
@@ -232,12 +261,60 @@ const CameraConfigContent = memo(function CameraConfigContent({
component: ReviewSection,
},
{ key: "audio", i18nNamespace: "config/audio", component: AudioSection },
{
key: "audio_transcription",
i18nNamespace: "config/audio_transcription",
component: AudioTranscriptionSection,
showOverrideIndicator: true,
},
{
key: "notifications",
i18nNamespace: "config/notifications",
component: NotificationsSection,
},
{ key: "live", i18nNamespace: "config/live", component: LiveSection },
{
key: "birdseye",
i18nNamespace: "config/birdseye",
component: BirdseyeSection,
showOverrideIndicator: true,
},
{
key: "face_recognition",
i18nNamespace: "config/face_recognition",
component: FaceRecognitionSection,
showOverrideIndicator: true,
},
{
key: "lpr",
i18nNamespace: "config/lpr",
component: LprSection,
showOverrideIndicator: true,
},
{
key: "semantic_search",
i18nNamespace: "config/semantic_search",
component: SemanticSearchSection,
showOverrideIndicator: false,
},
{
key: "mqtt",
i18nNamespace: "config/camera_mqtt",
component: CameraMqttSection,
showOverrideIndicator: false,
},
{
key: "onvif",
i18nNamespace: "config/onvif",
component: OnvifSection,
showOverrideIndicator: false,
},
{
key: "ui",
i18nNamespace: "config/camera_ui",
component: CameraUiSection,
showOverrideIndicator: false,
},
{
key: "timestamp_style",
i18nNamespace: "config/timestamp_style",
@@ -273,9 +350,9 @@ const CameraConfigContent = memo(function CameraConfigContent({
<span>{sectionLabel}</span>
{isOverridden && (
<Badge variant="secondary" className="h-5 px-1.5 text-xs">
{t("button.modified", {
{t("button.overridden", {
ns: "common",
defaultValue: "Modified",
defaultValue: "Overridden",
})}
</Badge>
)}
@@ -298,7 +375,7 @@ const CameraConfigContent = memo(function CameraConfigContent({
<SectionComponent
level="camera"
cameraName={cameraName}
showOverrideIndicator
showOverrideIndicator={section.showOverrideIndicator !== false}
onSave={onSave}
showTitle={true}
/>
+239 -22
View File
@@ -79,6 +79,7 @@ const globalSectionConfigs: Record<
"topic_prefix",
"client_id",
"stats_interval",
"qos",
"tls_ca_certs",
"tls_client_cert",
"tls_client_key",
@@ -86,6 +87,7 @@ const globalSectionConfigs: Record<
],
advancedFields: [
"stats_interval",
"qos",
"tls_ca_certs",
"tls_client_cert",
"tls_client_key",
@@ -102,17 +104,71 @@ const globalSectionConfigs: Record<
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",
"admin_first_time_login",
],
advancedFields: [
"cookie_name",
"cookie_secure",
"session_length",
"refresh_time",
"failed_login_rate_limit",
"trusted_proxies",
"hash_iterations",
"roles",
"admin_first_time_login",
],
advancedFields: ["failed_login_rate_limit", "trusted_proxies"],
},
tls: {
i18nNamespace: "config/tls",
fieldOrder: ["enabled", "cert", "key"],
advancedFields: [],
},
networking: {
i18nNamespace: "config/networking",
fieldOrder: ["ipv6"],
advancedFields: [],
},
proxy: {
i18nNamespace: "config/proxy",
fieldOrder: [
"header_map",
"logout_url",
"auth_secret",
"default_role",
"separator",
],
advancedFields: ["header_map", "auth_secret", "separator"],
},
ui: {
i18nNamespace: "config/ui",
fieldOrder: [
"timezone",
"time_format",
"date_style",
"time_style",
"unit_system",
],
advancedFields: [],
},
logger: {
i18nNamespace: "config/logger",
fieldOrder: ["default", "logs"],
advancedFields: ["logs"],
},
environment_vars: {
i18nNamespace: "config/environment_vars",
fieldOrder: [],
advancedFields: [],
},
telemetry: {
i18nNamespace: "config/telemetry",
fieldOrder: ["network_interfaces", "stats", "version_check"],
@@ -129,39 +185,191 @@ const globalSectionConfigs: Record<
"mode",
"layout",
"inactivity_threshold",
"idle_heartbeat_fps",
],
advancedFields: ["width", "height", "quality", "inactivity_threshold"],
},
ffmpeg: {
i18nNamespace: "config/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",
],
},
detectors: {
i18nNamespace: "config/detectors",
fieldOrder: [],
advancedFields: [],
},
model: {
i18nNamespace: "config/model",
fieldOrder: [
"path",
"labelmap_path",
"width",
"height",
"input_pixel_format",
"input_tensor",
"input_dtype",
"model_type",
"labelmap",
"attributes_map",
],
advancedFields: [
"labelmap",
"attributes_map",
"input_pixel_format",
"input_tensor",
"input_dtype",
"model_type",
],
},
genai: {
i18nNamespace: "config/genai",
fieldOrder: [
"provider",
"api_key",
"base_url",
"model",
"provider_options",
"runtime_options",
],
advancedFields: ["base_url", "provider_options", "runtime_options"],
},
classification: {
i18nNamespace: "config/classification",
fieldOrder: ["bird", "custom"],
advancedFields: [],
},
semantic_search: {
i18nNamespace: "config/semantic_search",
fieldOrder: ["enabled", "reindex", "model_size"],
advancedFields: ["reindex"],
fieldOrder: ["enabled", "reindex", "model", "model_size", "device"],
advancedFields: ["reindex", "device"],
},
audio_transcription: {
i18nNamespace: "config/audio_transcription",
fieldOrder: ["enabled", "language", "device", "model_size", "live_enabled"],
advancedFields: ["language", "device", "model_size"],
},
face_recognition: {
i18nNamespace: "config/face_recognition",
fieldOrder: ["enabled", "threshold", "min_area", "model_size"],
advancedFields: ["threshold", "min_area"],
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: {
i18nNamespace: "config/lpr",
fieldOrder: [
"enabled",
"threshold",
"min_area",
"min_ratio",
"max_ratio",
"model_size",
"detection_threshold",
"min_area",
"recognition_threshold",
"min_plate_length",
"format",
"match_distance",
"known_plates",
"enhancement",
"debug_save_plates",
"device",
"replace_rules",
],
advancedFields: ["threshold", "min_area", "min_ratio", "max_ratio"],
advancedFields: [
"detection_threshold",
"recognition_threshold",
"min_plate_length",
"format",
"match_distance",
"known_plates",
"enhancement",
"debug_save_plates",
"device",
"replace_rules",
],
},
go2rtc: {
i18nNamespace: "config/go2rtc",
fieldOrder: [],
advancedFields: [],
},
camera_groups: {
i18nNamespace: "config/camera_groups",
fieldOrder: ["cameras", "icon", "order"],
advancedFields: [],
},
safe_mode: {
i18nNamespace: "config/safe_mode",
fieldOrder: [],
advancedFields: [],
},
version: {
i18nNamespace: "config/version",
fieldOrder: [],
advancedFields: [],
},
};
// System sections (global only)
const systemSections = ["database", "tls", "auth", "telemetry", "birdseye"];
const systemSections = [
"database",
"tls",
"auth",
"networking",
"proxy",
"ui",
"logger",
"environment_vars",
"telemetry",
"birdseye",
"ffmpeg",
"detectors",
"model",
"classification",
"go2rtc",
"camera_groups",
"safe_mode",
"version",
];
// Integration sections (global only)
const integrationSections = [
"mqtt",
"audio_transcription",
"genai",
"semantic_search",
"face_recognition",
"lpr",
@@ -186,18 +394,12 @@ const GlobalConfigSection = memo(function GlobalConfigSection({
"views/settings",
"common",
]);
const [pendingData, setPendingData] = useState<Record<
string,
unknown
> | null>(null);
const [pendingData, setPendingData] = useState<unknown | null>(null);
const [isSaving, setIsSaving] = useState(false);
const formData = useMemo((): Record<string, unknown> => {
if (!config) return {} as Record<string, unknown>;
const value = (config as unknown as Record<string, unknown>)[sectionKey];
return (
(value as Record<string, unknown>) || ({} as Record<string, unknown>)
);
const formData = useMemo((): unknown => {
if (!config) return {};
return (config as unknown as Record<string, unknown>)[sectionKey];
}, [config, sectionKey]);
const hasChanges = useMemo(() => {
@@ -205,7 +407,7 @@ const GlobalConfigSection = memo(function GlobalConfigSection({
return !isEqual(formData, pendingData);
}, [formData, pendingData]);
const handleChange = useCallback((data: Record<string, unknown>) => {
const handleChange = useCallback((data: unknown) => {
setPendingData(data);
}, []);
@@ -300,14 +502,29 @@ export default function GlobalConfigView() {
"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");