This commit is contained in:
Josh Hawkins 2026-01-29 11:30:54 -06:00
parent bc5c29fda1
commit 3b5b4bffb9

View File

@ -1,488 +0,0 @@
---
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<string, any>; // 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.