From 398a3a7b9516d8900bd87bd8f213b89b482cd81a Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:29:52 -0500 Subject: [PATCH] Rename nickname to friendly_name (#19782) Better aligns with convention from Home Assistant since many Frigate users are also HA users --- docs/docs/configuration/reference.md | 2 +- frigate/comms/webpush.py | 4 ++-- frigate/config/camera/camera.py | 8 +++++--- frigate/storage.py | 2 +- web/src/components/camera/CameraNameLabel.tsx | 4 ++-- web/src/components/overlay/CameraInfoDialog.tsx | 4 ++-- web/src/components/overlay/CreateTriggerDialog.tsx | 4 ++-- web/src/components/player/LivePlayer.tsx | 4 ++-- web/src/components/player/PreviewPlayer.tsx | 6 +++--- web/src/components/settings/CameraEditForm.tsx | 14 +++++++------- .../components/settings/CameraStreamingDialog.tsx | 4 ++-- ...era-nickname.ts => use-camera-friendly-name.ts} | 6 +++--- web/src/hooks/use-stats.ts | 4 ++-- web/src/types/frigateConfig.ts | 2 +- web/src/views/settings/CameraSettingsView.tsx | 4 ++-- web/src/views/settings/ObjectSettingsView.tsx | 4 ++-- web/src/views/settings/TriggerView.tsx | 4 ++-- web/src/views/system/CameraMetrics.tsx | 2 +- 18 files changed, 42 insertions(+), 40 deletions(-) rename web/src/hooks/{use-camera-nickname.ts => use-camera-friendly-name.ts} (78%) diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index 7b324801b..9b2127b83 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -899,7 +899,7 @@ cameras: # Optional: Configuration for triggers to automate actions based on semantic search results. triggers: - # Required: Unique identifier for the trigger (generated automatically from nickname if not specified). + # Required: Unique identifier for the trigger (generated automatically from friendly_name if not specified). trigger_name: # Required: Enable or disable the trigger. (default: shown below) enabled: true diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index 20d84ed5c..f518a2d94 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -335,7 +335,7 @@ class WebPushClient(Communicator): camera: str = payload["after"]["camera"] camera_name: str = getattr( - self.config.cameras[camera], "nickname", None + self.config.cameras[camera], "friendly_name", None ) or titlecase(camera.replace("_", " ")) current_time = datetime.datetime.now().timestamp() @@ -410,7 +410,7 @@ class WebPushClient(Communicator): camera: str = payload["camera"] camera_name: str = getattr( - self.config.cameras[camera], "nickname", None + self.config.cameras[camera], "friendly_name", None ) or titlecase(camera.replace("_", " ")) current_time = datetime.datetime.now().timestamp() diff --git a/frigate/config/camera/camera.py b/frigate/config/camera/camera.py index 4290c8bf2..68f874138 100644 --- a/frigate/config/camera/camera.py +++ b/frigate/config/camera/camera.py @@ -52,12 +52,14 @@ class CameraTypeEnum(str, Enum): class CameraConfig(FrigateBaseModel): name: Optional[str] = Field(None, title="Camera name.", pattern=REGEX_CAMERA_NAME) - nickname: Optional[str] = Field(None, title="Camera nickname. Only for display.") + friendly_name: Optional[str] = Field( + None, title="Camera friendly name used in the Frigate UI." + ) @model_validator(mode="before") @classmethod - def handle_nickname(cls, values): - if isinstance(values, dict) and "nickname" in values: + def handle_friendly_name(cls, values): + if isinstance(values, dict) and "friendly_name" in values: pass return values diff --git a/frigate/storage.py b/frigate/storage.py index ddd1eceed..611412e1e 100644 --- a/frigate/storage.py +++ b/frigate/storage.py @@ -78,7 +78,7 @@ class StorageMaintainer(threading.Thread): ) camera_key = ( - getattr(self.config.cameras[camera], "nickname", None) or camera + getattr(self.config.cameras[camera], "friendly_name", None) or camera ) usages[camera_key] = { "usage": camera_storage, diff --git a/web/src/components/camera/CameraNameLabel.tsx b/web/src/components/camera/CameraNameLabel.tsx index c05502b32..ab022f5c8 100644 --- a/web/src/components/camera/CameraNameLabel.tsx +++ b/web/src/components/camera/CameraNameLabel.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import * as LabelPrimitive from "@radix-ui/react-label"; -import { useCameraNickname } from "@/hooks/use-camera-nickname"; +import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; import { CameraConfig } from "@/types/frigateConfig"; interface CameraNameLabelProps @@ -12,7 +12,7 @@ const CameraNameLabel = React.forwardRef< React.ElementRef, CameraNameLabelProps >(({ className, camera, ...props }, ref) => { - const displayName = useCameraNickname(camera); + const displayName = useCameraFriendlyName(camera); return ( {displayName} diff --git a/web/src/components/overlay/CameraInfoDialog.tsx b/web/src/components/overlay/CameraInfoDialog.tsx index 65d472417..a15d9c590 100644 --- a/web/src/components/overlay/CameraInfoDialog.tsx +++ b/web/src/components/overlay/CameraInfoDialog.tsx @@ -16,7 +16,7 @@ import axios from "axios"; import { toast } from "sonner"; import { Toaster } from "../ui/sonner"; import { Trans, useTranslation } from "react-i18next"; -import { useCameraNickname } from "@/hooks/use-camera-nickname"; +import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; type CameraInfoDialogProps = { camera: CameraConfig; @@ -75,7 +75,7 @@ export default function CameraInfoDialog({ return b === 0 ? a : gcd(b, a % b); } - const cameraName = useCameraNickname(camera); + const cameraName = useCameraFriendlyName(camera); return ( <> diff --git a/web/src/components/overlay/CreateTriggerDialog.tsx b/web/src/components/overlay/CreateTriggerDialog.tsx index 235caffb1..4d21dc6c8 100644 --- a/web/src/components/overlay/CreateTriggerDialog.tsx +++ b/web/src/components/overlay/CreateTriggerDialog.tsx @@ -37,7 +37,7 @@ import ImagePicker from "@/components/overlay/ImagePicker"; import { Trigger, TriggerAction, TriggerType } from "@/types/trigger"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "../ui/textarea"; -import { useCameraNickname } from "@/hooks/use-camera-nickname"; +import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; type CreateTriggerDialogProps = { show: boolean; @@ -162,7 +162,7 @@ export default function CreateTriggerDialog({ onCancel(); }; - const cameraName = useCameraNickname(selectedCamera); + const cameraName = useCameraFriendlyName(selectedCamera); return ( diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 26834973f..5d3b6aa7f 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -24,7 +24,7 @@ import { baseUrl } from "@/api/baseUrl"; import { PlayerStats } from "./PlayerStats"; import { LuVideoOff } from "react-icons/lu"; import { Trans, useTranslation } from "react-i18next"; -import { useCameraNickname } from "@/hooks/use-camera-nickname"; +import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; type LivePlayerProps = { cameraRef?: (ref: HTMLDivElement | null) => void; @@ -77,7 +77,7 @@ export default function LivePlayer({ const internalContainerRef = useRef(null); - const cameraName = useCameraNickname(cameraConfig); + const cameraName = useCameraFriendlyName(cameraConfig); // stats const [stats, setStats] = useState({ diff --git a/web/src/components/player/PreviewPlayer.tsx b/web/src/components/player/PreviewPlayer.tsx index 13cf3d576..c08c73396 100644 --- a/web/src/components/player/PreviewPlayer.tsx +++ b/web/src/components/player/PreviewPlayer.tsx @@ -21,7 +21,7 @@ import { usePreviewForTimeRange, } from "@/hooks/use-camera-previews"; import { useTranslation } from "react-i18next"; -import { useCameraNickname } from "@/hooks/use-camera-nickname"; +import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; type PreviewPlayerProps = { previewRef?: (ref: HTMLDivElement | null) => void; @@ -149,7 +149,7 @@ function PreviewVideoPlayer({ const { t } = useTranslation(["components/player"]); const { data: config } = useSWR("config"); - const cameraName = useCameraNickname(camera); + const cameraName = useCameraFriendlyName(camera); // controlling playback const previewRef = useRef(null); @@ -466,7 +466,7 @@ function PreviewFramesPlayer({ }: PreviewFramesPlayerProps) { const { t } = useTranslation(["components/player"]); - const cameraName = useCameraNickname(camera); + const cameraName = useCameraFriendlyName(camera); // frames data const { data: previewFrames } = useSWR( diff --git a/web/src/components/settings/CameraEditForm.tsx b/web/src/components/settings/CameraEditForm.tsx index 86c7d1e93..983a8167d 100644 --- a/web/src/components/settings/CameraEditForm.tsx +++ b/web/src/components/settings/CameraEditForm.tsx @@ -107,7 +107,7 @@ export default function CameraEditForm({ const cameraInfo = useMemo(() => { if (!cameraName || !config?.cameras[cameraName]) { return { - nickname: undefined, + friendly_name: undefined, name: cameraName || "", roles: new Set(), }; @@ -121,14 +121,14 @@ export default function CameraEditForm({ }); return { - nickname: camera?.nickname || cameraName, + friendly_name: camera?.friendly_name || cameraName, name: cameraName, roles, }; }, [cameraName, config]); const defaultValues: FormValues = { - cameraName: cameraInfo?.nickname || cameraName || "", + cameraName: cameraInfo?.friendly_name || cameraName || "", enabled: true, ffmpeg: { inputs: [ @@ -169,18 +169,18 @@ export default function CameraEditForm({ const saveCameraConfig = (values: FormValues) => { setIsLoading(true); let finalCameraName = values.cameraName; - let nickname: string | undefined = undefined; + let friendly_name: string | undefined = undefined; const isValidName = /^[a-zA-Z0-9_-]+$/.test(values.cameraName); if (!isValidName) { finalCameraName = generateFixedHash(finalCameraName); - nickname = values.cameraName; + friendly_name = values.cameraName; } const configData: ConfigSetBody["config_data"] = { cameras: { [finalCameraName]: { enabled: values.enabled, - ...(nickname && { nickname }), + ...(friendly_name && { friendly_name }), ffmpeg: { inputs: values.ffmpeg.inputs.map((input) => ({ path: input.path, @@ -235,7 +235,7 @@ export default function CameraEditForm({ if ( cameraName && values.cameraName !== cameraName && - values.cameraName !== cameraInfo?.nickname + values.cameraName !== cameraInfo?.friendly_name ) { // If camera name changed, delete old camera config const deleteRequestBody: ConfigSetBody = { diff --git a/web/src/components/settings/CameraStreamingDialog.tsx b/web/src/components/settings/CameraStreamingDialog.tsx index c49e186be..62b0a1472 100644 --- a/web/src/components/settings/CameraStreamingDialog.tsx +++ b/web/src/components/settings/CameraStreamingDialog.tsx @@ -33,7 +33,7 @@ import { Link } from "react-router-dom"; import { LiveStreamMetadata } from "@/types/live"; import { Trans, useTranslation } from "react-i18next"; import { useDocDomain } from "@/hooks/use-doc-domain"; -import { useCameraNickname } from "@/hooks/use-camera-nickname"; +import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; type CameraStreamingDialogProps = { camera: string; @@ -57,7 +57,7 @@ export function CameraStreamingDialog({ const { getLocaleDocUrl } = useDocDomain(); const { data: config } = useSWR("config"); - const cameraName = useCameraNickname(camera); + const cameraName = useCameraFriendlyName(camera); const [isLoading, setIsLoading] = useState(false); diff --git a/web/src/hooks/use-camera-nickname.ts b/web/src/hooks/use-camera-friendly-name.ts similarity index 78% rename from web/src/hooks/use-camera-nickname.ts rename to web/src/hooks/use-camera-friendly-name.ts index 5f5fe9cf1..c484dc83d 100644 --- a/web/src/hooks/use-camera-nickname.ts +++ b/web/src/hooks/use-camera-friendly-name.ts @@ -8,14 +8,14 @@ export function resolveCameraName( ) { if (typeof cameraId === "object" && cameraId !== null) { const camera = cameraId as CameraConfig; - return camera?.nickname || camera?.name.replaceAll("_", " "); + return camera?.friendly_name || camera?.name.replaceAll("_", " "); } else { const camera = config?.cameras?.[String(cameraId)]; - return camera?.nickname || String(cameraId).replaceAll("_", " "); + return camera?.friendly_name || String(cameraId).replaceAll("_", " "); } } -export function useCameraNickname( +export function useCameraFriendlyName( cameraId: string | CameraConfig | undefined, ): string { const { data: config } = useSWR("config"); diff --git a/web/src/hooks/use-stats.ts b/web/src/hooks/use-stats.ts index 48d9b6f2a..566020b05 100644 --- a/web/src/hooks/use-stats.ts +++ b/web/src/hooks/use-stats.ts @@ -61,7 +61,7 @@ export default function useStats(stats: FrigateStats | undefined) { return; } - const cameraName = config.cameras?.[name]?.nickname ?? name; + const cameraName = config.cameras?.[name]?.friendly_name ?? name; if (config.cameras[name].enabled && cam["camera_fps"] == 0) { problems.push({ text: t("stats.cameraIsOffline", { @@ -82,7 +82,7 @@ export default function useStats(stats: FrigateStats | undefined) { memoizedStats["cpu_usages"][cam["pid"]]?.cpu_average, ); - const cameraName = config?.cameras?.[name]?.nickname ?? name; + const cameraName = config?.cameras?.[name]?.friendly_name ?? name; if (!isNaN(ffmpegAvg) && ffmpegAvg >= CameraFfmpegThreshold.error) { problems.push({ text: t("stats.ffmpegHighCpuUsage", { diff --git a/web/src/types/frigateConfig.ts b/web/src/types/frigateConfig.ts index eb806d7b5..e3cc6455a 100644 --- a/web/src/types/frigateConfig.ts +++ b/web/src/types/frigateConfig.ts @@ -33,7 +33,7 @@ export type SearchModel = "jinav1" | "jinav2"; export type SearchModelSize = "small" | "large"; export interface CameraConfig { - nickname: string; + friendly_name: string; audio: { enabled: boolean; enabled_in_config: boolean; diff --git a/web/src/views/settings/CameraSettingsView.tsx b/web/src/views/settings/CameraSettingsView.tsx index 8894a5b8f..cc30806e5 100644 --- a/web/src/views/settings/CameraSettingsView.tsx +++ b/web/src/views/settings/CameraSettingsView.tsx @@ -49,7 +49,7 @@ import { } from "@/components/ui/select"; import { IoMdArrowRoundBack } from "react-icons/io"; import { isDesktop } from "react-device-detect"; -import { useCameraNickname } from "@/hooks/use-camera-nickname"; +import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; import { CameraNameLabel } from "@/components/camera/CameraNameLabel"; type CameraSettingsViewProps = { @@ -98,7 +98,7 @@ export default function CameraSettingsView({ return []; }, [config]); - const selectCameraName = useCameraNickname(selectedCamera); + const selectCameraName = useCameraFriendlyName(selectedCamera); // zones and labels diff --git a/web/src/views/settings/ObjectSettingsView.tsx b/web/src/views/settings/ObjectSettingsView.tsx index 6c6363f9d..b5b08adcc 100644 --- a/web/src/views/settings/ObjectSettingsView.tsx +++ b/web/src/views/settings/ObjectSettingsView.tsx @@ -30,7 +30,7 @@ import { isDesktop } from "react-device-detect"; import { Trans, useTranslation } from "react-i18next"; import { useDocDomain } from "@/hooks/use-doc-domain"; import { getTranslatedLabel } from "@/utils/i18n"; -import { useCameraNickname } from "@/hooks/use-camera-nickname"; +import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; import { AudioLevelGraph } from "@/components/audio/AudioLevelGraph"; import { useWs } from "@/api/ws"; @@ -129,7 +129,7 @@ export default function ObjectSettingsView({ } }, [config, selectedCamera]); - const cameraName = useCameraNickname(cameraConfig); + const cameraName = useCameraFriendlyName(cameraConfig); const { objects, audio_detections } = useCameraActivity( cameraConfig ?? ({} as CameraConfig), diff --git a/web/src/views/settings/TriggerView.tsx b/web/src/views/settings/TriggerView.tsx index 61270484f..74b1eff3b 100644 --- a/web/src/views/settings/TriggerView.tsx +++ b/web/src/views/settings/TriggerView.tsx @@ -23,7 +23,7 @@ import { cn } from "@/lib/utils"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { Link } from "react-router-dom"; import { useTriggers } from "@/api/ws"; -import { useCameraNickname } from "@/hooks/use-camera-nickname"; +import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; type ConfigSetBody = { requires_restart: number; @@ -79,7 +79,7 @@ export default function TriggerView({ const [triggeredTrigger, setTriggeredTrigger] = useState(); const [isLoading, setIsLoading] = useState(false); - const cameraName = useCameraNickname(selectedCamera); + const cameraName = useCameraFriendlyName(selectedCamera); const triggers = useMemo(() => { if ( !config || diff --git a/web/src/views/system/CameraMetrics.tsx b/web/src/views/system/CameraMetrics.tsx index cd23c339b..c55c9f24c 100644 --- a/web/src/views/system/CameraMetrics.tsx +++ b/web/src/views/system/CameraMetrics.tsx @@ -14,7 +14,7 @@ import { import useSWR from "swr"; import { useTranslation } from "react-i18next"; import { CameraNameLabel } from "@/components/camera/CameraNameLabel"; -import { resolveCameraName } from "@/hooks/use-camera-nickname"; +import { resolveCameraName } from "@/hooks/use-camera-friendly-name"; type CameraMetricsProps = { lastUpdated: number;