Rename nickname to friendly_name (#19782)

Better aligns with convention from Home Assistant since many Frigate users are also HA users
This commit is contained in:
Josh Hawkins 2025-08-26 15:29:52 -05:00 committed by GitHub
parent d3af748366
commit 398a3a7b95
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 42 additions and 40 deletions

View File

@ -899,7 +899,7 @@ cameras:
# Optional: Configuration for triggers to automate actions based on semantic search results. # Optional: Configuration for triggers to automate actions based on semantic search results.
triggers: 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: trigger_name:
# Required: Enable or disable the trigger. (default: shown below) # Required: Enable or disable the trigger. (default: shown below)
enabled: true enabled: true

View File

@ -335,7 +335,7 @@ class WebPushClient(Communicator):
camera: str = payload["after"]["camera"] camera: str = payload["after"]["camera"]
camera_name: str = getattr( camera_name: str = getattr(
self.config.cameras[camera], "nickname", None self.config.cameras[camera], "friendly_name", None
) or titlecase(camera.replace("_", " ")) ) or titlecase(camera.replace("_", " "))
current_time = datetime.datetime.now().timestamp() current_time = datetime.datetime.now().timestamp()
@ -410,7 +410,7 @@ class WebPushClient(Communicator):
camera: str = payload["camera"] camera: str = payload["camera"]
camera_name: str = getattr( camera_name: str = getattr(
self.config.cameras[camera], "nickname", None self.config.cameras[camera], "friendly_name", None
) or titlecase(camera.replace("_", " ")) ) or titlecase(camera.replace("_", " "))
current_time = datetime.datetime.now().timestamp() current_time = datetime.datetime.now().timestamp()

View File

@ -52,12 +52,14 @@ class CameraTypeEnum(str, Enum):
class CameraConfig(FrigateBaseModel): class CameraConfig(FrigateBaseModel):
name: Optional[str] = Field(None, title="Camera name.", pattern=REGEX_CAMERA_NAME) 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") @model_validator(mode="before")
@classmethod @classmethod
def handle_nickname(cls, values): def handle_friendly_name(cls, values):
if isinstance(values, dict) and "nickname" in values: if isinstance(values, dict) and "friendly_name" in values:
pass pass
return values return values

View File

@ -78,7 +78,7 @@ class StorageMaintainer(threading.Thread):
) )
camera_key = ( camera_key = (
getattr(self.config.cameras[camera], "nickname", None) or camera getattr(self.config.cameras[camera], "friendly_name", None) or camera
) )
usages[camera_key] = { usages[camera_key] = {
"usage": camera_storage, "usage": camera_storage,

View File

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label"; 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"; import { CameraConfig } from "@/types/frigateConfig";
interface CameraNameLabelProps interface CameraNameLabelProps
@ -12,7 +12,7 @@ const CameraNameLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
CameraNameLabelProps CameraNameLabelProps
>(({ className, camera, ...props }, ref) => { >(({ className, camera, ...props }, ref) => {
const displayName = useCameraNickname(camera); const displayName = useCameraFriendlyName(camera);
return ( return (
<LabelPrimitive.Root ref={ref} className={className} {...props}> <LabelPrimitive.Root ref={ref} className={className} {...props}>
{displayName} {displayName}

View File

@ -16,7 +16,7 @@ import axios from "axios";
import { toast } from "sonner"; import { toast } from "sonner";
import { Toaster } from "../ui/sonner"; import { Toaster } from "../ui/sonner";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { useCameraNickname } from "@/hooks/use-camera-nickname"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
type CameraInfoDialogProps = { type CameraInfoDialogProps = {
camera: CameraConfig; camera: CameraConfig;
@ -75,7 +75,7 @@ export default function CameraInfoDialog({
return b === 0 ? a : gcd(b, a % b); return b === 0 ? a : gcd(b, a % b);
} }
const cameraName = useCameraNickname(camera); const cameraName = useCameraFriendlyName(camera);
return ( return (
<> <>

View File

@ -37,7 +37,7 @@ import ImagePicker from "@/components/overlay/ImagePicker";
import { Trigger, TriggerAction, TriggerType } from "@/types/trigger"; import { Trigger, TriggerAction, TriggerType } from "@/types/trigger";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Textarea } from "../ui/textarea"; import { Textarea } from "../ui/textarea";
import { useCameraNickname } from "@/hooks/use-camera-nickname"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
type CreateTriggerDialogProps = { type CreateTriggerDialogProps = {
show: boolean; show: boolean;
@ -162,7 +162,7 @@ export default function CreateTriggerDialog({
onCancel(); onCancel();
}; };
const cameraName = useCameraNickname(selectedCamera); const cameraName = useCameraFriendlyName(selectedCamera);
return ( return (
<Dialog open={show} onOpenChange={onCancel}> <Dialog open={show} onOpenChange={onCancel}>

View File

@ -24,7 +24,7 @@ import { baseUrl } from "@/api/baseUrl";
import { PlayerStats } from "./PlayerStats"; import { PlayerStats } from "./PlayerStats";
import { LuVideoOff } from "react-icons/lu"; import { LuVideoOff } from "react-icons/lu";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { useCameraNickname } from "@/hooks/use-camera-nickname"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
type LivePlayerProps = { type LivePlayerProps = {
cameraRef?: (ref: HTMLDivElement | null) => void; cameraRef?: (ref: HTMLDivElement | null) => void;
@ -77,7 +77,7 @@ export default function LivePlayer({
const internalContainerRef = useRef<HTMLDivElement | null>(null); const internalContainerRef = useRef<HTMLDivElement | null>(null);
const cameraName = useCameraNickname(cameraConfig); const cameraName = useCameraFriendlyName(cameraConfig);
// stats // stats
const [stats, setStats] = useState<PlayerStatsType>({ const [stats, setStats] = useState<PlayerStatsType>({

View File

@ -21,7 +21,7 @@ import {
usePreviewForTimeRange, usePreviewForTimeRange,
} from "@/hooks/use-camera-previews"; } from "@/hooks/use-camera-previews";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useCameraNickname } from "@/hooks/use-camera-nickname"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
type PreviewPlayerProps = { type PreviewPlayerProps = {
previewRef?: (ref: HTMLDivElement | null) => void; previewRef?: (ref: HTMLDivElement | null) => void;
@ -149,7 +149,7 @@ function PreviewVideoPlayer({
const { t } = useTranslation(["components/player"]); const { t } = useTranslation(["components/player"]);
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const cameraName = useCameraNickname(camera); const cameraName = useCameraFriendlyName(camera);
// controlling playback // controlling playback
const previewRef = useRef<HTMLVideoElement | null>(null); const previewRef = useRef<HTMLVideoElement | null>(null);
@ -466,7 +466,7 @@ function PreviewFramesPlayer({
}: PreviewFramesPlayerProps) { }: PreviewFramesPlayerProps) {
const { t } = useTranslation(["components/player"]); const { t } = useTranslation(["components/player"]);
const cameraName = useCameraNickname(camera); const cameraName = useCameraFriendlyName(camera);
// frames data // frames data
const { data: previewFrames } = useSWR<string[]>( const { data: previewFrames } = useSWR<string[]>(

View File

@ -107,7 +107,7 @@ export default function CameraEditForm({
const cameraInfo = useMemo(() => { const cameraInfo = useMemo(() => {
if (!cameraName || !config?.cameras[cameraName]) { if (!cameraName || !config?.cameras[cameraName]) {
return { return {
nickname: undefined, friendly_name: undefined,
name: cameraName || "", name: cameraName || "",
roles: new Set<Role>(), roles: new Set<Role>(),
}; };
@ -121,14 +121,14 @@ export default function CameraEditForm({
}); });
return { return {
nickname: camera?.nickname || cameraName, friendly_name: camera?.friendly_name || cameraName,
name: cameraName, name: cameraName,
roles, roles,
}; };
}, [cameraName, config]); }, [cameraName, config]);
const defaultValues: FormValues = { const defaultValues: FormValues = {
cameraName: cameraInfo?.nickname || cameraName || "", cameraName: cameraInfo?.friendly_name || cameraName || "",
enabled: true, enabled: true,
ffmpeg: { ffmpeg: {
inputs: [ inputs: [
@ -169,18 +169,18 @@ export default function CameraEditForm({
const saveCameraConfig = (values: FormValues) => { const saveCameraConfig = (values: FormValues) => {
setIsLoading(true); setIsLoading(true);
let finalCameraName = values.cameraName; let finalCameraName = values.cameraName;
let nickname: string | undefined = undefined; let friendly_name: string | undefined = undefined;
const isValidName = /^[a-zA-Z0-9_-]+$/.test(values.cameraName); const isValidName = /^[a-zA-Z0-9_-]+$/.test(values.cameraName);
if (!isValidName) { if (!isValidName) {
finalCameraName = generateFixedHash(finalCameraName); finalCameraName = generateFixedHash(finalCameraName);
nickname = values.cameraName; friendly_name = values.cameraName;
} }
const configData: ConfigSetBody["config_data"] = { const configData: ConfigSetBody["config_data"] = {
cameras: { cameras: {
[finalCameraName]: { [finalCameraName]: {
enabled: values.enabled, enabled: values.enabled,
...(nickname && { nickname }), ...(friendly_name && { friendly_name }),
ffmpeg: { ffmpeg: {
inputs: values.ffmpeg.inputs.map((input) => ({ inputs: values.ffmpeg.inputs.map((input) => ({
path: input.path, path: input.path,
@ -235,7 +235,7 @@ export default function CameraEditForm({
if ( if (
cameraName && cameraName &&
values.cameraName !== cameraName && values.cameraName !== cameraName &&
values.cameraName !== cameraInfo?.nickname values.cameraName !== cameraInfo?.friendly_name
) { ) {
// If camera name changed, delete old camera config // If camera name changed, delete old camera config
const deleteRequestBody: ConfigSetBody = { const deleteRequestBody: ConfigSetBody = {

View File

@ -33,7 +33,7 @@ import { Link } from "react-router-dom";
import { LiveStreamMetadata } from "@/types/live"; import { LiveStreamMetadata } from "@/types/live";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { useDocDomain } from "@/hooks/use-doc-domain"; import { useDocDomain } from "@/hooks/use-doc-domain";
import { useCameraNickname } from "@/hooks/use-camera-nickname"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
type CameraStreamingDialogProps = { type CameraStreamingDialogProps = {
camera: string; camera: string;
@ -57,7 +57,7 @@ export function CameraStreamingDialog({
const { getLocaleDocUrl } = useDocDomain(); const { getLocaleDocUrl } = useDocDomain();
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const cameraName = useCameraNickname(camera); const cameraName = useCameraFriendlyName(camera);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);

View File

@ -8,14 +8,14 @@ export function resolveCameraName(
) { ) {
if (typeof cameraId === "object" && cameraId !== null) { if (typeof cameraId === "object" && cameraId !== null) {
const camera = cameraId as CameraConfig; const camera = cameraId as CameraConfig;
return camera?.nickname || camera?.name.replaceAll("_", " "); return camera?.friendly_name || camera?.name.replaceAll("_", " ");
} else { } else {
const camera = config?.cameras?.[String(cameraId)]; 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, cameraId: string | CameraConfig | undefined,
): string { ): string {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");

View File

@ -61,7 +61,7 @@ export default function useStats(stats: FrigateStats | undefined) {
return; 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) { if (config.cameras[name].enabled && cam["camera_fps"] == 0) {
problems.push({ problems.push({
text: t("stats.cameraIsOffline", { text: t("stats.cameraIsOffline", {
@ -82,7 +82,7 @@ export default function useStats(stats: FrigateStats | undefined) {
memoizedStats["cpu_usages"][cam["pid"]]?.cpu_average, 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) { if (!isNaN(ffmpegAvg) && ffmpegAvg >= CameraFfmpegThreshold.error) {
problems.push({ problems.push({
text: t("stats.ffmpegHighCpuUsage", { text: t("stats.ffmpegHighCpuUsage", {

View File

@ -33,7 +33,7 @@ export type SearchModel = "jinav1" | "jinav2";
export type SearchModelSize = "small" | "large"; export type SearchModelSize = "small" | "large";
export interface CameraConfig { export interface CameraConfig {
nickname: string; friendly_name: string;
audio: { audio: {
enabled: boolean; enabled: boolean;
enabled_in_config: boolean; enabled_in_config: boolean;

View File

@ -49,7 +49,7 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { IoMdArrowRoundBack } from "react-icons/io"; import { IoMdArrowRoundBack } from "react-icons/io";
import { isDesktop } from "react-device-detect"; 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"; import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
type CameraSettingsViewProps = { type CameraSettingsViewProps = {
@ -98,7 +98,7 @@ export default function CameraSettingsView({
return []; return [];
}, [config]); }, [config]);
const selectCameraName = useCameraNickname(selectedCamera); const selectCameraName = useCameraFriendlyName(selectedCamera);
// zones and labels // zones and labels

View File

@ -30,7 +30,7 @@ import { isDesktop } from "react-device-detect";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { useDocDomain } from "@/hooks/use-doc-domain"; import { useDocDomain } from "@/hooks/use-doc-domain";
import { getTranslatedLabel } from "@/utils/i18n"; 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 { AudioLevelGraph } from "@/components/audio/AudioLevelGraph";
import { useWs } from "@/api/ws"; import { useWs } from "@/api/ws";
@ -129,7 +129,7 @@ export default function ObjectSettingsView({
} }
}, [config, selectedCamera]); }, [config, selectedCamera]);
const cameraName = useCameraNickname(cameraConfig); const cameraName = useCameraFriendlyName(cameraConfig);
const { objects, audio_detections } = useCameraActivity( const { objects, audio_detections } = useCameraActivity(
cameraConfig ?? ({} as CameraConfig), cameraConfig ?? ({} as CameraConfig),

View File

@ -23,7 +23,7 @@ import { cn } from "@/lib/utils";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useTriggers } from "@/api/ws"; import { useTriggers } from "@/api/ws";
import { useCameraNickname } from "@/hooks/use-camera-nickname"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
type ConfigSetBody = { type ConfigSetBody = {
requires_restart: number; requires_restart: number;
@ -79,7 +79,7 @@ export default function TriggerView({
const [triggeredTrigger, setTriggeredTrigger] = useState<string>(); const [triggeredTrigger, setTriggeredTrigger] = useState<string>();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const cameraName = useCameraNickname(selectedCamera); const cameraName = useCameraFriendlyName(selectedCamera);
const triggers = useMemo(() => { const triggers = useMemo(() => {
if ( if (
!config || !config ||

View File

@ -14,7 +14,7 @@ import {
import useSWR from "swr"; import useSWR from "swr";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { CameraNameLabel } from "@/components/camera/CameraNameLabel"; import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
import { resolveCameraName } from "@/hooks/use-camera-nickname"; import { resolveCameraName } from "@/hooks/use-camera-friendly-name";
type CameraMetricsProps = { type CameraMetricsProps = {
lastUpdated: number; lastUpdated: number;