diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md index 28be554d5..626e2f528 100644 --- a/docs/docs/configuration/index.md +++ b/docs/docs/configuration/index.md @@ -436,4 +436,12 @@ cameras: quality: 70 # Optional: Restrict mqtt messages to objects that entered any of the listed zones (default: no required zones) required_zones: [] + + # Optional: Configuration for how camera is handled in the GUI. + ui: + # Optional: Adjust sort order of cameras in the UI. Larger numbers come later (default: shown below) + # By default the cameras are sorted alphabetically. + order: 0 + # Optional: Whether or not to show the camera in the Frigate UI (default: shown below) + dashboard: True ``` diff --git a/frigate/config.py b/frigate/config.py index c3830c4af..fd89907e4 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -514,6 +514,11 @@ class CameraLiveConfig(FrigateBaseModel): quality: int = Field(default=8, ge=1, le=31, title="Live camera view quality") +class CameraUiConfig(FrigateBaseModel): + order: int = Field(default=0, title="Order of camera in UI.") + dashboard: bool = Field(default=True, title="Show this camera in Frigate dashboard UI.") + + class CameraConfig(FrigateBaseModel): name: Optional[str] = Field(title="Camera name.", regex="^[a-zA-Z0-9_-]+$") ffmpeg: CameraFfmpegConfig = Field(title="FFmpeg configuration for the camera.") @@ -546,6 +551,9 @@ class CameraConfig(FrigateBaseModel): detect: DetectConfig = Field( default_factory=DetectConfig, title="Object detection configuration." ) + ui: CameraUiConfig = Field( + default_factory=CameraUiConfig, title="Camera UI Modifications." + ) birdseye: BirdseyeCameraConfig = Field( default_factory=BirdseyeCameraConfig, title="Birdseye camera configuration." ) diff --git a/web/config/handlers.js b/web/config/handlers.js index e2e95d077..7afd23109 100644 --- a/web/config/handlers.js +++ b/web/config/handlers.js @@ -20,6 +20,7 @@ export const handlers = [ detect: { width: 1280, height: 720 }, snapshots: {}, live: { height: 720 }, + ui: { dashboard: true, order: 0 }, }, side: { name: 'side', @@ -28,6 +29,7 @@ export const handlers = [ detect: { width: 1280, height: 720 }, snapshots: {}, live: { height: 720 }, + ui: { dashboard: true, order: 1 }, }, }, }) diff --git a/web/src/Sidebar.jsx b/web/src/Sidebar.jsx index 457f7c400..a74a8c80b 100644 --- a/web/src/Sidebar.jsx +++ b/web/src/Sidebar.jsx @@ -3,15 +3,27 @@ import LinkedLogo from './components/LinkedLogo'; import { Match } from 'preact-router/match'; import { memo } from 'preact/compat'; import { ENV } from './env'; +import { useMemo } from 'preact/hooks' import useSWR from 'swr'; import NavigationDrawer, { Destination, Separator } from './components/NavigationDrawer'; export default function Sidebar() { const { data: config } = useSWR('config'); + + const sortedCameras = useMemo(() => { + if (!config) { + return []; + } + + return Object.entries(config.cameras) + .filter(([_, conf]) => conf.ui.dashboard) + .sort(([_, aConf], [__, bConf]) => aConf.ui.order - bConf.ui.order); + }, [config]); + if (!config) { return null; } - const { cameras, birdseye } = config; + const { birdseye } = config; return ( }> @@ -19,31 +31,14 @@ export default function Sidebar() { {({ matches }) => matches ? ( - - - {Object.keys(cameras).map((camera) => ( - - ))} - - + ) : null } {({ matches }) => matches ? ( - - - {Object.keys(cameras).map((camera) => ( - - ))} - - + ) : null } @@ -64,10 +59,43 @@ export default function Sidebar() { ); } +function CameraSection({ sortedCameras }) { + + return ( + + + {sortedCameras.map(([camera]) => ( + + ))} + + + ); +} + +function RecordingSection({ sortedCameras }) { + + return ( + + + {sortedCameras.map(([camera, _]) => { + return ( + + ); + })} + + + ); +} + const Header = memo(() => { return (
); -}); +}); \ No newline at end of file diff --git a/web/src/routes/Cameras.jsx b/web/src/routes/Cameras.jsx index efe2e9b7c..a61aff91d 100644 --- a/web/src/routes/Cameras.jsx +++ b/web/src/routes/Cameras.jsx @@ -1,4 +1,4 @@ -import { h } from 'preact'; +import { h, Fragment } from 'preact'; import ActivityIndicator from '../components/ActivityIndicator'; import Card from '../components/Card'; import CameraImage from '../components/CameraImage'; @@ -16,10 +16,25 @@ export default function Cameras() { ) : (
- {Object.entries(config.cameras).map(([camera, conf]) => ( + +
+ ); +} + +function SortedCameras({ unsortedCameras }) { + + const sortedCameras = useMemo(() => + Object.entries(unsortedCameras) + .filter(([_, conf]) => conf.ui.dashboard) + .sort(([_, aConf], [__, bConf]) => aConf.ui.order - bConf.ui.order), + [unsortedCameras]); + + return ( + + {sortedCameras.map(([camera, conf]) => ( ))} - + ); }