refactor configs to use individual files with a template

This commit is contained in:
Josh Hawkins
2026-02-27 09:40:21 -06:00
parent c9c29b7c33
commit a97333d881
40 changed files with 1111 additions and 1057 deletions
+34 -110
View File
@@ -4,24 +4,7 @@
import { useMemo, useCallback, useState, memo } from "react";
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { DetectSection } from "@/components/config-form/sections/DetectSection";
import { RecordSection } from "@/components/config-form/sections/RecordSection";
import { SnapshotsSection } from "@/components/config-form/sections/SnapshotsSection";
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 { TimestampSection } from "@/components/config-form/sections/TimestampSection";
import { ConfigSectionTemplate } from "@/components/config-form/sections";
import { useAllCameraOverrides } from "@/hooks/use-config-override";
import type { FrigateConfig } from "@/types/frigateConfig";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -203,83 +186,26 @@ const CameraConfigContent = memo(function CameraConfigContent({
const sections: Array<{
key: string;
component: typeof DetectSection;
showOverrideIndicator?: boolean;
}> = [
{
key: "detect",
component: DetectSection,
},
{
key: "ffmpeg",
component: FfmpegSection,
showOverrideIndicator: true,
},
{
key: "record",
component: RecordSection,
},
{
key: "snapshots",
component: SnapshotsSection,
},
{
key: "motion",
component: MotionSection,
},
{
key: "objects",
component: ObjectsSection,
},
{
key: "review",
component: ReviewSection,
},
{ key: "audio", component: AudioSection },
{
key: "audio_transcription",
component: AudioTranscriptionSection,
showOverrideIndicator: true,
},
{
key: "notifications",
component: NotificationsSection,
},
{ key: "live", component: LiveSection },
{
key: "birdseye",
component: BirdseyeSection,
showOverrideIndicator: true,
},
{
key: "face_recognition",
component: FaceRecognitionSection,
showOverrideIndicator: true,
},
{
key: "lpr",
component: LprSection,
showOverrideIndicator: true,
},
{
key: "mqtt",
component: CameraMqttSection,
showOverrideIndicator: false,
},
{
key: "onvif",
component: OnvifSection,
showOverrideIndicator: false,
},
{
key: "ui",
component: CameraUiSection,
showOverrideIndicator: false,
},
{
key: "timestamp_style",
component: TimestampSection,
},
{ key: "detect" },
{ key: "ffmpeg", showOverrideIndicator: true },
{ key: "record" },
{ key: "snapshots" },
{ key: "motion" },
{ key: "objects" },
{ key: "review" },
{ key: "audio" },
{ key: "audio_transcription", showOverrideIndicator: true },
{ key: "notifications" },
{ key: "live" },
{ key: "birdseye", showOverrideIndicator: true },
{ key: "face_recognition", showOverrideIndicator: true },
{ key: "lpr", showOverrideIndicator: true },
{ key: "mqtt", showOverrideIndicator: false },
{ key: "onvif", showOverrideIndicator: false },
{ key: "ui", showOverrideIndicator: false },
{ key: "timestamp_style" },
];
return (
@@ -327,23 +253,21 @@ const CameraConfigContent = memo(function CameraConfigContent({
{/* Section Content */}
<div className="scrollbar-container flex-1 overflow-y-auto pr-4">
{sections.map((section) => {
const SectionComponent = section.component;
return (
<div
key={section.key}
className={cn(activeSection === section.key ? "block" : "hidden")}
>
<SectionComponent
level="camera"
cameraName={cameraName}
showOverrideIndicator={section.showOverrideIndicator !== false}
onSave={onSave}
showTitle={true}
/>
</div>
);
})}
{sections.map((section) => (
<div
key={section.key}
className={cn(activeSection === section.key ? "block" : "hidden")}
>
<ConfigSectionTemplate
sectionKey={section.key}
level="camera"
cameraName={cameraName}
showOverrideIndicator={section.showOverrideIndicator !== false}
onSave={onSave}
showTitle={true}
/>
</div>
))}
</div>
</div>
);
+45 -78
View File
@@ -4,81 +4,51 @@
import { useMemo, useCallback, useState } from "react";
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { DetectSection } from "@/components/config-form/sections/DetectSection";
import { RecordSection } from "@/components/config-form/sections/RecordSection";
import { SnapshotsSection } from "@/components/config-form/sections/SnapshotsSection";
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 { 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 { 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 { TelemetrySection } from "@/components/config-form/sections/TelemetrySection";
import { TlsSection } from "@/components/config-form/sections/TlsSection";
import { UiSection } from "@/components/config-form/sections/UiSection";
import { ConfigSectionTemplate } from "@/components/config-form/sections";
import type { FrigateConfig } from "@/types/frigateConfig";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import Heading from "@/components/ui/heading";
import { cn } from "@/lib/utils";
import { getSectionConfig } from "@/components/config-form/sectionConfigs";
// Shared sections that can be overridden at camera level
const sharedSections = [
{ key: "detect", component: DetectSection },
{ key: "record", component: RecordSection },
{ key: "snapshots", component: SnapshotsSection },
{ key: "motion", component: MotionSection },
{ key: "objects", component: ObjectsSection },
{ key: "review", component: ReviewSection },
{ key: "audio", component: AudioSection },
{ key: "live", component: LiveSection },
{ key: "timestamp_style", component: TimestampSection },
{ key: "detect" },
{ key: "record" },
{ key: "snapshots" },
{ key: "motion" },
{ key: "objects" },
{ key: "review" },
{ key: "audio" },
{ key: "live" },
{ key: "timestamp_style" },
];
// System sections (global only)
const systemSections = [
{ key: "database", component: DatabaseSection },
{ key: "tls", component: TlsSection },
{ key: "auth", component: AuthSection },
{ key: "networking", component: NetworkingSection },
{ key: "proxy", component: ProxySection },
{ key: "ui", component: UiSection },
{ key: "logger", component: LoggerSection },
{ key: "environment_vars", component: EnvironmentVarsSection },
{ key: "telemetry", component: TelemetrySection },
{ key: "birdseye", component: BirdseyeSection },
{ key: "ffmpeg", component: FfmpegSection },
{ key: "detectors", component: DetectorsSection },
{ key: "model", component: ModelSection },
{ key: "database" },
{ key: "tls" },
{ key: "auth" },
{ key: "networking" },
{ key: "proxy" },
{ key: "ui" },
{ key: "logger" },
{ key: "environment_vars" },
{ key: "telemetry" },
{ key: "birdseye" },
{ key: "ffmpeg" },
{ key: "detectors" },
{ key: "model" },
];
// Integration sections (global only)
const integrationSections = [
{ key: "mqtt", component: MqttSection },
{ key: "semantic_search", component: SemanticSearchSection },
{ key: "genai", component: GenaiSection },
{ key: "face_recognition", component: FaceRecognitionSection },
{ key: "lpr", component: LprSection },
{ key: "classification", component: ClassificationSection },
{ key: "audio_transcription", component: AudioTranscriptionSection },
{ key: "mqtt" },
{ key: "semantic_search" },
{ key: "genai" },
{ key: "face_recognition" },
{ key: "lpr" },
{ key: "classification" },
{ key: "audio_transcription" },
];
export default function GlobalConfigView() {
@@ -201,24 +171,21 @@ export default function GlobalConfigView() {
{/* Section Content */}
<div className="scrollbar-container flex-1 overflow-y-auto pr-4">
{currentSections.map((section) => {
const SectionComponent = section.component;
return (
<div
key={section.key}
className={cn(
activeSection === section.key ? "block" : "hidden",
)}
>
<SectionComponent
level="global"
onSave={handleSave}
showTitle={true}
sectionConfig={getSectionConfig(section.key, "global")}
/>
</div>
);
})}
{currentSections.map((section) => (
<div
key={section.key}
className={cn(
activeSection === section.key ? "block" : "hidden",
)}
>
<ConfigSectionTemplate
sectionKey={section.key}
level="global"
onSave={handleSave}
showTitle={true}
/>
</div>
))}
</div>
</div>
</Tabs>
+4 -7
View File
@@ -1,9 +1,7 @@
import { useTranslation } from "react-i18next";
import Heading from "@/components/ui/heading";
import type {
BaseSectionProps,
SectionConfig,
} from "@/components/config-form/sections";
import type { SectionConfig } from "@/components/config-form/sections";
import { ConfigSectionTemplate } from "@/components/config-form/sections";
import type { PolygonType } from "@/types/canvas";
export type SettingsPageProps = {
@@ -15,7 +13,6 @@ export type SettingsPageProps = {
export type SingleSectionPageOptions = {
sectionKey: string;
level: "global" | "camera";
SectionComponent: React.ComponentType<BaseSectionProps>;
sectionConfig?: SectionConfig;
requiresRestart?: boolean;
showOverrideIndicator?: boolean;
@@ -24,7 +21,6 @@ export type SingleSectionPageOptions = {
export function createSingleSectionPage({
sectionKey,
level,
SectionComponent,
sectionConfig,
requiresRestart,
showOverrideIndicator = true,
@@ -63,7 +59,8 @@ export function createSingleSectionPage({
</p>
)}
</div>
<SectionComponent
<ConfigSectionTemplate
sectionKey={sectionKey}
level={level}
cameraName={level === "camera" ? selectedCamera : undefined}
showOverrideIndicator={showOverrideIndicator}