mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-26 21:59:02 +03:00
feat: Add camera nickname (#19567)
* refactor: Refactor camera nickname * fix: fix cameraNameLabel visually * chore: The Explore search function also displays the Camera's nickname in English * chore: add mobile page camera nickname * feat: webpush support camera nickname * fix: fix storage camera name is null * chore: fix review detail and context menu camera nickname * chore: fix use-stats and notification setting camera nickname * fix: fix stats camera if not nickname need capitalize * fix: fix debug page open camera web ui i18n and camera nickname support * fix: fix camera metrics not use nickname * refactor: refactor use-camera-nickname hook.
This commit is contained in:
@@ -30,6 +30,12 @@ type ConfigSetBody = {
|
||||
config_data: any;
|
||||
update_topic?: string;
|
||||
};
|
||||
const generateFixedHash = (name: string): string => {
|
||||
const encoded = encodeURIComponent(name);
|
||||
const base64 = btoa(encoded);
|
||||
const cleanHash = base64.replace(/[^a-zA-Z0-9]/g, "").substring(0, 8);
|
||||
return `cam_${cleanHash.toLowerCase()}`;
|
||||
};
|
||||
|
||||
const RoleEnum = z.enum(["audio", "detect", "record"]);
|
||||
type Role = z.infer<typeof RoleEnum>;
|
||||
@@ -54,10 +60,7 @@ export default function CameraEditForm({
|
||||
z.object({
|
||||
cameraName: z
|
||||
.string()
|
||||
.min(1, { message: t("camera.cameraConfig.nameRequired") })
|
||||
.regex(/^[a-zA-Z0-9_-]+$/, {
|
||||
message: t("camera.cameraConfig.nameInvalid"),
|
||||
}),
|
||||
.min(1, { message: t("camera.cameraConfig.nameRequired") }),
|
||||
enabled: z.boolean(),
|
||||
ffmpeg: z.object({
|
||||
inputs: z
|
||||
@@ -101,26 +104,37 @@ export default function CameraEditForm({
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
// Determine available roles for default values
|
||||
const usedRoles = useMemo(() => {
|
||||
const roles = new Set<Role>();
|
||||
if (cameraName && config?.cameras[cameraName]) {
|
||||
const camera = config.cameras[cameraName];
|
||||
camera.ffmpeg?.inputs?.forEach((input) => {
|
||||
input.roles.forEach((role) => roles.add(role as Role));
|
||||
});
|
||||
const cameraInfo = useMemo(() => {
|
||||
if (!cameraName || !config?.cameras[cameraName]) {
|
||||
return {
|
||||
nickname: undefined,
|
||||
name: cameraName || "",
|
||||
roles: new Set<Role>(),
|
||||
};
|
||||
}
|
||||
return roles;
|
||||
|
||||
const camera = config.cameras[cameraName];
|
||||
const roles = new Set<Role>();
|
||||
|
||||
camera.ffmpeg?.inputs?.forEach((input) => {
|
||||
input.roles.forEach((role) => roles.add(role as Role));
|
||||
});
|
||||
|
||||
return {
|
||||
nickname: camera?.nickname || cameraName,
|
||||
name: cameraName,
|
||||
roles,
|
||||
};
|
||||
}, [cameraName, config]);
|
||||
|
||||
const defaultValues: FormValues = {
|
||||
cameraName: cameraName || "",
|
||||
cameraName: cameraInfo?.nickname || cameraName || "",
|
||||
enabled: true,
|
||||
ffmpeg: {
|
||||
inputs: [
|
||||
{
|
||||
path: "",
|
||||
roles: usedRoles.has("detect") ? [] : ["detect"],
|
||||
roles: cameraInfo.roles.has("detect") ? [] : ["detect"],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -154,10 +168,19 @@ export default function CameraEditForm({
|
||||
|
||||
const saveCameraConfig = (values: FormValues) => {
|
||||
setIsLoading(true);
|
||||
let finalCameraName = values.cameraName;
|
||||
let nickname: string | undefined = undefined;
|
||||
const isValidName = /^[a-zA-Z0-9_-]+$/.test(values.cameraName);
|
||||
if (!isValidName) {
|
||||
finalCameraName = generateFixedHash(finalCameraName);
|
||||
nickname = values.cameraName;
|
||||
}
|
||||
|
||||
const configData: ConfigSetBody["config_data"] = {
|
||||
cameras: {
|
||||
[values.cameraName]: {
|
||||
[finalCameraName]: {
|
||||
enabled: values.enabled,
|
||||
...(nickname && { nickname }),
|
||||
ffmpeg: {
|
||||
inputs: values.ffmpeg.inputs.map((input) => ({
|
||||
path: input.path,
|
||||
@@ -175,7 +198,7 @@ export default function CameraEditForm({
|
||||
|
||||
// Add update_topic for new cameras
|
||||
if (!cameraName) {
|
||||
requestBody.update_topic = `config/cameras/${values.cameraName}/add`;
|
||||
requestBody.update_topic = `config/cameras/${finalCameraName}/add`;
|
||||
}
|
||||
|
||||
axios
|
||||
@@ -209,7 +232,11 @@ export default function CameraEditForm({
|
||||
};
|
||||
|
||||
const onSubmit = (values: FormValues) => {
|
||||
if (cameraName && values.cameraName !== cameraName) {
|
||||
if (
|
||||
cameraName &&
|
||||
values.cameraName !== cameraName &&
|
||||
values.cameraName !== cameraInfo?.nickname
|
||||
) {
|
||||
// If camera name changed, delete old camera config
|
||||
const deleteRequestBody: ConfigSetBody = {
|
||||
requires_restart: 1,
|
||||
|
||||
@@ -33,6 +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";
|
||||
|
||||
type CameraStreamingDialogProps = {
|
||||
camera: string;
|
||||
@@ -56,6 +57,8 @@ export function CameraStreamingDialog({
|
||||
const { getLocaleDocUrl } = useDocDomain();
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
|
||||
const cameraName = useCameraNickname(camera);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [streamName, setStreamName] = useState(
|
||||
@@ -190,7 +193,7 @@ export function CameraStreamingDialog({
|
||||
<DialogHeader className="mb-4">
|
||||
<DialogTitle className="smart-capitalize">
|
||||
{t("group.camera.setting.title", {
|
||||
cameraName: camera.replaceAll("_", " "),
|
||||
cameraName: cameraName,
|
||||
})}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
||||
Reference in New Issue
Block a user