--- task: UI Configuration using react-jsonschema-form slug: ui-config-rjsf created: 2026-01-21 status: planning --- # UI Configuration using react-jsonschema-form ## Overview Implement a comprehensive configuration UI for Frigate NVR using react-jsonschema-form (RJSF), driven by the existing Pydantic configuration schema. The UI should allow users to configure Frigate through a web interface instead of manually editing YAML files, with proper validation, reusable components, and clear visual indicators for global vs camera-level settings. ## Research Findings ### Key Components | Component | Location | Purpose | | ---------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------- | | FrigateConfig | [frigate/config/config.py](frigate/config/config.py#L297-L412) | Root Pydantic model defining complete Frigate configuration | | CameraConfig | [frigate/config/camera/camera.py](frigate/config/camera/camera.py#L49-L130) | Camera-level configuration model | | FrigateBaseModel | [frigate/config/base.py](frigate/config/base.py) | Base model with `extra="forbid"` validation | | Config API | [frigate/api/app.py](frigate/api/app.py#L367-L456) | PUT /config/set endpoint for saving config | | Schema Endpoint | [frigate/api/app.py](frigate/api/app.py#L73-L77) | GET /config/schema.json - exposes Pydantic schema | | Settings Page | [web/src/pages/Settings.tsx](web/src/pages/Settings.tsx) | Main settings page with sidebar navigation | | Form Components | [web/src/components/ui/form.tsx](web/src/components/ui/form.tsx) | Existing react-hook-form based form components | ### Architecture The Frigate configuration system is structured as follows: 1. **Pydantic Models**: All configuration is defined using Pydantic v2 models in `frigate/config/`. The `FrigateConfig` class is the root model. 2. **Schema Generation**: Pydantic automatically generates JSON Schema via `model_json_schema()`. The schema is already exposed at `/api/config/schema.json` and used by the ConfigEditor (monaco-yaml) for validation. 3. **Config API**: The `/config/set` endpoint accepts configuration updates in two formats: - Query string parameters (e.g., `?cameras.front.detect.enabled=True`) - JSON body via `config_data` field in `AppConfigSetBody` The JSON body is preferred. 4. **Frontend Stack**: - React 18 with TypeScript - Radix UI primitives + Tailwind CSS (shadcn/ui patterns) - react-hook-form + zod for form validation - SWR for data fetching - react-i18next for translations - axios for API calls ### Data Flow 1. **Config Loading**: `useSWR("config")` fetches current config from `/api/config` 2. **Schema Loading**: JSON Schema available at `/api/config/schema.json` 3. **Config Updates**: PUT to `/config/set` with JSON body 4. **Real-time Updates**: Some settings support `requires_restart: 0` for live updates via WebSocket pub/sub ### Reusable Config Sections (Global + Camera Level) Based on schema analysis, these sections appear at BOTH global and camera levels using the **same** Pydantic model: | Section | Model | Notes | | --------------- | -------------------- | ------------------------------------------------------------------- | | audio | AudioConfig | Full model reuse | | detect | DetectConfig | Full model reuse, nested StationaryConfig | | live | CameraLiveConfig | Full model reuse | | motion | MotionConfig | Full model reuse | | notifications | NotificationConfig | Full model reuse | | objects | ObjectConfig | Full model reuse, nested FilterConfig, GenAIObjectConfig | | record | RecordConfig | Full model reuse, nested EventsConfig, RetainConfig | | review | ReviewConfig | Full model reuse, nested ReviewAlertsConfig, ReviewDetectionsConfig | | snapshots | SnapshotsConfig | Full model reuse, nested SnapshotsRetainConfig | | timestamp_style | TimestampStyleConfig | Full model reuse, nested ColorConfig | **Different models at global vs camera level** (require conditional handling): | Section | Global Model | Camera Model | Notes | | ------------------- | ----------------------------- | ----------------------------------- | -------------------- | | audio_transcription | AudioTranscriptionConfig | CameraAudioTranscriptionConfig | Camera has subset | | birdseye | BirdseyeConfig | BirdseyeCameraConfig | Different fields | | face_recognition | FaceRecognitionConfig | CameraFaceRecognitionConfig | Camera has subset | | ffmpeg | FfmpegConfig | CameraFfmpegConfig | Camera adds inputs[] | | lpr | LicensePlateRecognitionConfig | CameraLicensePlateRecognitionConfig | Camera has subset | | semantic_search | SemanticSearchConfig | CameraSemanticSearchConfig | Completely different | ### Special Field Types Requiring Custom Widgets | Type | Examples | Widget Needed | | --------------------- | --------------------------------------------------- | ------------------------------ | | Enums | BirdseyeModeEnum, RetainModeEnum, RecordQualityEnum | Select dropdown | | list[str] | objects.track, zones[], required_zones | Tag input / Multi-select | | Union[str, list[str]] | mask fields | Text or array input | | dict[str, T] | zones, objects.filters | Key-value editor / Nested form | | ColorConfig | timestamp_style.color (RGB) | Color picker | | Coordinates/Masks | zone.coordinates | Polygon editor (existing) | | Password fields | mqtt.password | Password input with show/hide | ### Current UI Patterns 1. **Settings Navigation**: [web/src/pages/Settings.tsx](web/src/pages/Settings.tsx#L68-L114) uses a sidebar with grouped sections 2. **Camera Selection**: Many views accept `selectedCamera` prop for per-camera settings 3. **Form Pattern**: Uses react-hook-form with zod schemas (see [CameraReviewSettingsView.tsx](web/src/views/settings/CameraReviewSettingsView.tsx#L113-L124)) 4. **Save Pattern**: Axios PUT to `config/set?key=value` with `requires_restart` flag 5. **Translations**: All strings in `web/public/locales/{lang}/views/` JSON files ### Dependencies **Internal**: - Existing form components: Form, FormField, FormItem, FormLabel, FormControl, FormMessage - UI primitives: Switch, Select, Input, Checkbox, Slider, Tabs - Existing hooks: useSWR, useTranslation, useOptimisticState **External (to add)**: - @rjsf/core (react-jsonschema-form core) - @rjsf/utils (utilities) - @rjsf/validator-ajv8 (JSON Schema validation) ### Configuration The `/config/set` API expects: ```typescript interface AppConfigSetBody { requires_restart: number; // 0 = live update, 1 = needs restart update_topic?: string; // For pub/sub notification config_data?: Record; // Bulk config updates } ``` Query string format: `?key.path.to.field=value` (e.g., `?cameras.front.detect.enabled=True`) ### Tests | Test File | Coverage | | ---------------------------------------------------- | ------------------------------------- | | No existing React component tests found | RJSF forms would benefit from testing | | Pydantic validation tests implicit in config loading | Schema validation ensures correctness | ## Implementation Plan ### Goal Users can configure all user-facing Frigate settings through a form-based UI with validation, clear global vs camera-level distinction, and proper handling of advanced settings. ### Current State Analysis - Schema already exposed at `/api/config/schema.json` - Settings page structure exists with sidebar navigation - Form components exist (react-hook-form based) - No react-jsonschema-form currently installed - Config set API supports both query strings and JSON body ### What We're NOT Doing - Replacing the raw YAML ConfigEditor (remains as advanced option) - Changing the Pydantic models structure - Modifying the config/set API endpoint - Auto-generating translations (manual translation required) ### Prerequisites - [x] Install @rjsf/core, @rjsf/utils, @rjsf/validator-ajv8 - [x] Create exclusion list and advanced settings list JSON files --- ## Phases | # | Phase | Status | Plan | Notes | | --- | -------------------------------- | ------- | ---- | ------------------------------------------------------------ | | 1 | Schema Pipeline & RJSF Setup | ✅ Done | — | Install deps, create schema transformer, set up RJSF theme | | 2 | Core Reusable Section Components | ✅ Done | — | Create shared components for detect, record, snapshots, etc. | | 3 | Global Configuration View | ✅ Done | — | Build main config sections (mqtt, auth, database, etc.) | | 4 | Camera Configuration with Tabs | ✅ Done | — | Multi-camera tabs, override indicators, section reuse | | 5 | Advanced Settings & Exclusions | ✅ Done | — | Progressive disclosure, contributor-editable lists | | 6 | Validation & Error Handling | ✅ Done | — | Inline errors, save blocking, API integration | | 7 | Integration & Polish | ✅ Done | — | Settings page integration, translations, documentation | **Status:** ⬜ Not Started → 📋 Planned → 🔄 In Progress → ✅ Done --- ## Phase Details ### Phase 1: Schema Pipeline & RJSF Setup **Overview**: Install react-jsonschema-form dependencies, create a schema transformation layer to convert Pydantic JSON Schema to RJSF-compatible format with UI customizations. **Changes Required**: 1. **Install Dependencies** - Add to package.json: @rjsf/core, @rjsf/utils, @rjsf/validator-ajv8 2. **Create Schema Transformer** - File: `web/src/lib/config-schema/` - Transform Pydantic schema to RJSF uiSchema - Handle nested $defs/references - Apply field ordering 3. **Create Custom RJSF Theme** - File: `web/src/components/config-form/theme/` - Map RJSF templates to existing shadcn/ui components - Custom widgets for special types (color, coordinates, etc.) **Success Criteria**: - RJSF renders basic form from schema - Existing UI component styling preserved - Schema fetching from /api/config/schema.json works ### Phase 2: Core Reusable Section Components **Overview**: Create composable section components for config areas that appear at both global and camera levels. **Changes Required**: 1. **Section Component Architecture** - File: `web/src/components/config-form/sections/` - Create: DetectSection, RecordSection, SnapshotsSection, MotionSection, ObjectsSection, ReviewSection, AudioSection, NotificationsSection, LiveSection, TimestampSection 2. **Each Section Component**: - Accepts `level: "global" | "camera"` prop - Accepts `cameraName?: string` for camera context - Accepts `showOverrideIndicator?: boolean` - Uses shared RJSF form with section-specific uiSchema 3. **Override Detection Hook** - File: `web/src/hooks/use-config-override.ts` - Compare camera value vs global default - Return override status for visual indicators 4. **Field Ordering and Layout Customization** **Requirement**: Field ordering and layout within each section must be easily customizable by contributors without requiring deep knowledge of RJSF internals. **Implementation Approach**: - Each reusable section component (DetectSection, RecordSection, etc.) should define its own field ordering and layout configuration - This can be accomplished as a TypeScript constant within the section component file itself - The configuration should specify: - Field display order - Field grouping (which fields appear together) - Layout hints (e.g., multiple fields per row, nested groupings) - Any section-specific uiSchema customizations **Example Structure**: ```typescript // In DetectSection.tsx or DetectSection.config.ts export const detectSectionConfig = { fieldOrder: [ "enabled", "fps", "width", "height", "max_disappeared", "stationary", ], fieldGroups: { resolution: ["width", "height"], performance: ["fps", "max_disappeared"], }, // ... other layout hints }; ``` **Success Criteria**: - Contributors can reorder fields by editing a clear configuration structure - No need to modify RJSF internals or complex uiSchema objects directly - Layout changes are localized to single files per section **Success Criteria**: - DetectSection renders identically at global and camera level - Override indicators show when camera differs from global - Adding new fields requires editing only section definition ### Phase 3: Global Configuration View **Overview**: Build the global configuration form for non-camera settings. **Changes Required**: 1. **Global Config View** - File: `web/src/views/settings/GlobalConfigView.tsx` - Sections: MQTT, Auth, Database, Telemetry, TLS, Proxy, Networking, UI, Detectors, Model, GenAI, Classification, Birdseye 2. **Per-Section Subforms** - Each section as collapsible card - Progressive disclosure for advanced fields - Individual save buttons per section OR unified save 3. **Translations** - File: `web/public/locales/en/views/settings.json` - Add keys for all config field labels/descriptions **Success Criteria**: - All global-only settings configurable - Proper field grouping and labels - Validation errors inline ### Phase 4: Camera Configuration with Tabs **Overview**: Create per-camera configuration with tab navigation and override indicators. **Changes Required**: 1. **Camera Config View** - File: `web/src/views/settings/CameraConfigView.tsx` - Tab per camera - Uses reusable section components 2. **Override Visual Indicators** - Badge/icon when field overrides global - "Reset to global default" action - Color coding (e.g., highlighted border) 3. **Camera-Specific Sections** - FFmpeg inputs configuration - Masks and Zones (link to existing editor) - ONVIF settings **Success Criteria**: - Switch between cameras via tabs - Clear visual distinction for overridden settings - Reset to global default works ### Phase 5: Advanced Settings & Exclusions **Overview**: Implement progressive disclosure and maintainable exclusion lists. **Changes Required**: 1. **Exclusion System** - Simple consts in the component for field names - Filter schema before rendering - Document exclusion format for contributors 2. **Advanced Fields Toggle** - "Show Advanced Settings" switch per section - Simple consts in the component for advanced field names - Default collapsed state **Success Criteria**: - Excluded fields never shown in UI - Advanced fields hidden by default ### Phase 6: Validation & Error Handling **Overview**: Ensure robust validation and user-friendly error messages. **Changes Required**: 1. **Client-Side Validation** - ajv8 validator with Pydantic schema - Custom error messages for common issues - Real-time validation on blur 2. **Server-Side Validation** - Handle 400 responses from /config/set - Parse Pydantic validation errors - Map to form fields 3. **Save Blocking** - Disable save button when invalid - Show error count badge - Scroll to first error on submit attempt **Success Criteria**: - Invalid forms cannot be saved - Errors shown inline next to fields - Clear error messages (not technical schema errors) ### Phase 7: Integration & Polish **Overview**: Integrate into settings page, finalize translations, and document. **Changes Required**: 1. **Settings Page Integration** - Add new views to settingsGroups in Settings.tsx - Sidebar navigation updates - Route configuration 2. **Translations** - All field labels and descriptions - Error messages - Section headers 3. **Documentation** - User-facing docs for UI configuration 4. **Testing** - Basic render tests for form components - Validation behavior tests - Save/cancel flow tests **Success Criteria**: - Seamless navigation from settings page - All strings translated - Documentation complete --- ## Testing Strategy ### Project Maturity Level Active Development - Frigate has extensive test infrastructure but limited frontend tests. ### Unit Tests - Schema transformer functions - Override detection hook - Custom widgets - Coverage target: 70% for new components ### Integration/Manual Tests - Full form render with live schema - Save/validation flow end-to-end - Camera tab switching - Override indicator accuracy - Mobile responsiveness --- ## Rollback Plan 1. All changes are additive - existing ConfigEditor remains functional 2. New views can be feature-flagged if needed 3. No database migrations required 4. No backend changes required (uses existing API) --- ## Component Hierarchy ``` web/src/ ├── components/ │ └── config-form/ │ ├── theme/ │ │ ├── index.ts # RJSF theme export │ │ ├── templates/ # Base templates (ObjectFieldTemplate, etc.) │ │ └── widgets/ # Custom widgets (ColorWidget, TagsWidget, etc.) │ ├── sections/ │ │ ├── DetectSection.tsx # Reusable for global + camera │ │ ├── RecordSection.tsx │ │ ├── SnapshotsSection.tsx │ │ ├── MotionSection.tsx │ │ ├── ObjectsSection.tsx │ │ ├── ReviewSection.tsx │ │ ├── AudioSection.tsx │ │ ├── NotificationsSection.tsx │ │ ├── LiveSection.tsx │ │ └── TimestampSection.tsx │ └── ConfigForm.tsx # Main form wrapper ├── lib/ │ └── config-schema/ │ ├── index.ts # Schema utilities │ ├── transformer.ts # Pydantic -> RJSF schema ├── hooks/ │ └── use-config-override.ts # Override detection └── views/ └── settings/ ├── GlobalConfigView.tsx # Global settings form └── CameraConfigView.tsx # Per-camera tabs form ``` --- ## Key Design Decisions 1. **RJSF over custom forms**: Leverage schema-driven forms for maintainability and automatic updates when Pydantic models change. 2. **Reusable sections via composition**: Same component renders at global and camera level, with props controlling context and override indicators. 3. **Existing UI primitives**: Custom RJSF theme wraps existing shadcn/ui components for visual consistency. 4. **Incremental adoption**: Existing settings views remain, new RJSF views added alongside.