Refactor birdseye activity modes as a list and add alerts/detections (#24012)

* backend

* tests

* frontend and i18n

* e2e test schema

* docs
This commit is contained in:
Josh Hawkins
2026-09-12 07:30:04 -06:00
committed by Nicolas Mowen
parent e77155a4f1
commit f4cf1e1539
29 changed files with 583 additions and 425 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -18
View File
@@ -70,25 +70,9 @@
"label": "Enable Birdseye",
"description": "Enable or disable the Birdseye view feature."
},
"mode": {
"modes": {
"label": "Activity types",
"description": "Activity types that include cameras in Birdseye.",
"continuous": {
"label": "Continuous",
"description": "Always include the camera in Birdseye."
},
"motion": {
"label": "Motion",
"description": "Include the camera in Birdseye when motion is detected."
},
"objects": {
"label": "Active objects",
"description": "Include the camera in Birdseye while an active object is tracked."
},
"stationary_objects": {
"label": "Stationary objects",
"description": "Include the camera in Birdseye while a stationary object is tracked."
}
"description": "Activity types that include cameras in Birdseye."
},
"order": {
"label": "Position",
+2 -18
View File
@@ -557,25 +557,9 @@
"label": "Enable Birdseye",
"description": "Enable or disable the Birdseye view feature."
},
"mode": {
"modes": {
"label": "Activity types",
"description": "Activity types that include cameras in Birdseye.",
"continuous": {
"label": "Continuous",
"description": "Always include the camera in Birdseye."
},
"motion": {
"label": "Motion",
"description": "Include the camera in Birdseye when motion is detected."
},
"objects": {
"label": "Active objects",
"description": "Include the camera in Birdseye while an active object is tracked."
},
"stationary_objects": {
"label": "Stationary objects",
"description": "Include the camera in Birdseye while a stationary object is tracked."
}
"description": "Activity types that include cameras in Birdseye."
},
"restream": {
"label": "Restream RTSP",
+10 -5
View File
@@ -1657,6 +1657,16 @@
"summary": "{{count}} selected",
"empty": "No zones available"
},
"birdseyeModes": {
"summary": "{{count}} selected",
"options": {
"continuous": "Continuous",
"motion": "Motion",
"all_objects": "All objects",
"alerts": "Alerts",
"detections": "Detections"
}
},
"inputRoles": {
"summary": "{{count}} roles selected",
"empty": "No roles available",
@@ -1856,11 +1866,6 @@
}
},
"birdseye": {
"trackingMode": {
"objects": "Objects",
"motion": "Motion",
"continuous": "Continuous"
},
"cameraOrder": {
"label": "Camera order",
"description": "Drag cameras to set their order in the Birdseye layout.",
@@ -10,29 +10,26 @@ const birdseye: SectionConfigOverrides = {
severity: "info",
condition: (ctx) => {
if (ctx.level !== "camera" || !ctx.fullCameraConfig) return false;
const mode = ctx.formData?.mode;
if (!mode || typeof mode !== "object" || Array.isArray(mode)) {
const modes = ctx.formData?.modes;
if (!Array.isArray(modes)) {
return false;
}
return (
(mode.objects === true || mode.stationary_objects === true) &&
modes.includes("all_objects") &&
ctx.fullCameraConfig.detect?.enabled === false
);
},
},
],
restartRequired: [],
fieldOrder: ["enabled", "mode", "order"],
fieldOrder: ["enabled", "modes", "order"],
hiddenFields: ["order"],
advancedFields: [],
overrideFields: ["enabled", "mode"],
overrideFields: ["enabled", "modes"],
uiSchema: {
mode: {
continuous: { "ui:size": "xs" },
motion: { "ui:size": "xs" },
objects: { "ui:size": "xs" },
stationary_objects: { "ui:size": "xs" },
modes: {
"ui:widget": "birdseyeModes",
},
},
},
@@ -43,7 +40,7 @@ const birdseye: SectionConfigOverrides = {
"width",
"height",
"quality",
"mode",
"modes",
"layout",
"inactivity_threshold",
"idle_heartbeat_fps",
@@ -59,7 +56,7 @@ const birdseye: SectionConfigOverrides = {
"idle_heartbeat_fps",
],
uiSchema: {
mode: {
modes: {
"ui:after": { render: "BirdseyeCameraReorder" },
},
},
@@ -22,6 +22,7 @@ import { ObjectLabelSwitchesWidget } from "./widgets/ObjectLabelSwitchesWidget";
import { AudioLabelSwitchesWidget } from "./widgets/AudioLabelSwitchesWidget";
import { ReviewLabelSwitchesWidget } from "./widgets/ReviewLabelSwitchesWidget";
import { ZoneSwitchesWidget } from "./widgets/ZoneSwitchesWidget";
import { BirdseyeModesWidget } from "./widgets/BirdseyeModesWidget";
import { ArrayAsTextWidget } from "./widgets/ArrayAsTextWidget";
import { FfmpegArgsWidget } from "./widgets/FfmpegArgsWidget";
import { GenAIModelWidget } from "./widgets/GenAIModelWidget";
@@ -87,6 +88,7 @@ export const frigateTheme: FrigateTheme = {
audioLabels: AudioLabelSwitchesWidget,
reviewLabels: ReviewLabelSwitchesWidget,
zoneNames: ZoneSwitchesWidget,
birdseyeModes: BirdseyeModesWidget,
timezoneSelect: TimezoneSelectWidget,
optionalField: OptionalFieldWidget,
semanticSearchModel: SemanticSearchModelWidget,
@@ -0,0 +1,44 @@
// Birdseye Modes Widget - For selecting Birdseye activity types via switches
import type { WidgetProps } from "@rjsf/utils";
import { SwitchesWidget } from "./SwitchesWidget";
import type { FormContext } from "./SwitchesWidget";
// order matches BirdseyeModeEnum in frigate/config/camera/birdseye.py
const BIRDSEYE_MODES: string[] = [
"continuous",
"motion",
"all_objects",
"alerts",
"detections",
];
function getBirdseyeModes(): string[] {
return BIRDSEYE_MODES;
}
function getBirdseyeModeLabel(mode: string, context?: FormContext): string {
const t = context?.t;
if (!t) {
return mode;
}
return t(`configForm.birdseyeModes.options.${mode}`, {
ns: "views/settings",
defaultValue: mode,
});
}
export function BirdseyeModesWidget(props: WidgetProps) {
return (
<SwitchesWidget
{...props}
options={{
...props.options,
getEntities: getBirdseyeModes,
getDisplayLabel: getBirdseyeModeLabel,
i18nKey: "birdseyeModes",
}}
/>
);
}
+1 -1
View File
@@ -480,7 +480,7 @@ export const OVERRIDABLE_SECTIONS: ReadonlyArray<{
key: "audio_transcription",
compareFields: ["enabled", "live_enabled"],
},
{ key: "birdseye", compareFields: ["enabled", "mode"] },
{ key: "birdseye", compareFields: ["enabled", "modes"] },
{ key: "face_recognition", compareFields: ["enabled", "min_area"] },
{
key: "ffmpeg",
+8 -8
View File
@@ -13,18 +13,18 @@ export interface UiConfig {
export interface BirdseyeConfig {
enabled: boolean;
height: number;
mode: BirdseyeModeConfig;
modes: BirdseyeMode[];
quality: number;
restream: boolean;
width: number;
}
export interface BirdseyeModeConfig {
continuous: boolean;
motion: boolean;
objects: boolean;
stationary_objects: boolean;
}
export type BirdseyeMode =
| "continuous"
| "motion"
| "all_objects"
| "alerts"
| "detections";
export interface FaceRecognitionConfig {
enabled: boolean;
@@ -56,7 +56,7 @@ export interface CameraConfig {
best_image_timeout: number;
birdseye: {
enabled: boolean;
mode: BirdseyeModeConfig;
modes: BirdseyeMode[];
order: number;
};
detect: {