mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-08-27 13:47:50 +02:00
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:
parent
d3af748366
commit
398a3a7b95
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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}
|
||||||
|
@ -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 (
|
||||||
<>
|
<>
|
||||||
|
@ -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}>
|
||||||
|
@ -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>({
|
||||||
|
@ -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[]>(
|
||||||
|
@ -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 = {
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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");
|
@ -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", {
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
@ -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 ||
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user