mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-21 23:58:22 +03:00
extract logic for friendly_name and use in wizard
This commit is contained in:
parent
3c86363f82
commit
84ac8d3654
@ -22,6 +22,7 @@ import { LuTrash2, LuPlus } from "react-icons/lu";
|
|||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
import { processCameraName } from "@/utils/cameraUtil";
|
||||||
|
|
||||||
type ConfigSetBody = {
|
type ConfigSetBody = {
|
||||||
requires_restart: number;
|
requires_restart: number;
|
||||||
@ -30,12 +31,6 @@ type ConfigSetBody = {
|
|||||||
config_data: any;
|
config_data: any;
|
||||||
update_topic?: string;
|
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"]);
|
const RoleEnum = z.enum(["audio", "detect", "record"]);
|
||||||
type Role = z.infer<typeof RoleEnum>;
|
type Role = z.infer<typeof RoleEnum>;
|
||||||
@ -168,19 +163,15 @@ export default function CameraEditForm({
|
|||||||
|
|
||||||
const saveCameraConfig = (values: FormValues) => {
|
const saveCameraConfig = (values: FormValues) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
let finalCameraName = values.cameraName;
|
const { finalCameraName, friendlyName } = processCameraName(
|
||||||
let friendly_name: string | undefined = undefined;
|
values.cameraName,
|
||||||
const isValidName = /^[a-zA-Z0-9_-]+$/.test(values.cameraName);
|
);
|
||||||
if (!isValidName) {
|
|
||||||
finalCameraName = generateFixedHash(finalCameraName);
|
|
||||||
friendly_name = values.cameraName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configData: ConfigSetBody["config_data"] = {
|
const configData: ConfigSetBody["config_data"] = {
|
||||||
cameras: {
|
cameras: {
|
||||||
[finalCameraName]: {
|
[finalCameraName]: {
|
||||||
enabled: values.enabled,
|
enabled: values.enabled,
|
||||||
...(friendly_name && { friendly_name }),
|
...(friendlyName && { friendly_name: friendlyName }),
|
||||||
ffmpeg: {
|
ffmpeg: {
|
||||||
inputs: values.ffmpeg.inputs.map((input) => ({
|
inputs: values.ffmpeg.inputs.map((input) => ({
|
||||||
path: input.path,
|
path: input.path,
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import type {
|
|||||||
CameraConfigData,
|
CameraConfigData,
|
||||||
ConfigSetBody,
|
ConfigSetBody,
|
||||||
} from "@/types/cameraWizard";
|
} from "@/types/cameraWizard";
|
||||||
|
import { processCameraName } from "@/utils/cameraUtil";
|
||||||
|
|
||||||
type WizardState = {
|
type WizardState = {
|
||||||
wizardData: Partial<WizardFormData>;
|
wizardData: Partial<WizardFormData>;
|
||||||
@ -158,12 +159,17 @@ export default function CameraWizardDialog({
|
|||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
|
// Process camera name and friendly name
|
||||||
|
const { finalCameraName, friendlyName } = processCameraName(
|
||||||
|
wizardData.cameraName,
|
||||||
|
);
|
||||||
|
|
||||||
// Convert wizard data to Frigate config format
|
// Convert wizard data to Frigate config format
|
||||||
const cameraName = wizardData.cameraName;
|
|
||||||
const configData: CameraConfigData = {
|
const configData: CameraConfigData = {
|
||||||
cameras: {
|
cameras: {
|
||||||
[cameraName]: {
|
[finalCameraName]: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
...(friendlyName && { friendly_name: friendlyName }),
|
||||||
ffmpeg: {
|
ffmpeg: {
|
||||||
inputs: wizardData.streams.map((stream, index) => {
|
inputs: wizardData.streams.map((stream, index) => {
|
||||||
const isRestreamed =
|
const isRestreamed =
|
||||||
@ -171,8 +177,8 @@ export default function CameraWizardDialog({
|
|||||||
if (isRestreamed) {
|
if (isRestreamed) {
|
||||||
const go2rtcStreamName =
|
const go2rtcStreamName =
|
||||||
wizardData.streams!.length === 1
|
wizardData.streams!.length === 1
|
||||||
? cameraName
|
? finalCameraName
|
||||||
: `${cameraName}_${index + 1}`;
|
: `${finalCameraName}_${index + 1}`;
|
||||||
return {
|
return {
|
||||||
path: `rtsp://127.0.0.1:8554/${go2rtcStreamName}`,
|
path: `rtsp://127.0.0.1:8554/${go2rtcStreamName}`,
|
||||||
input_args: "preset-rtsp-restream",
|
input_args: "preset-rtsp-restream",
|
||||||
@ -190,30 +196,26 @@ export default function CameraWizardDialog({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add friendly name if different from camera name
|
|
||||||
if (wizardData.cameraName !== cameraName) {
|
|
||||||
configData.cameras[cameraName].friendly_name = wizardData.cameraName;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add live.streams configuration for go2rtc streams
|
// Add live.streams configuration for go2rtc streams
|
||||||
if (wizardData.streams && wizardData.streams.length > 0) {
|
if (wizardData.streams && wizardData.streams.length > 0) {
|
||||||
configData.cameras[cameraName].live = {
|
configData.cameras[finalCameraName].live = {
|
||||||
streams: {},
|
streams: {},
|
||||||
};
|
};
|
||||||
wizardData.streams.forEach((_, index) => {
|
wizardData.streams.forEach((_, index) => {
|
||||||
const go2rtcStreamName =
|
const go2rtcStreamName =
|
||||||
wizardData.streams!.length === 1
|
wizardData.streams!.length === 1
|
||||||
? cameraName
|
? finalCameraName
|
||||||
: `${cameraName}_${index + 1}`;
|
: `${finalCameraName}_${index + 1}`;
|
||||||
configData.cameras[cameraName].live!.streams[`Stream ${index + 1}`] =
|
configData.cameras[finalCameraName].live!.streams[
|
||||||
go2rtcStreamName;
|
`Stream ${index + 1}`
|
||||||
|
] = go2rtcStreamName;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestBody: ConfigSetBody = {
|
const requestBody: ConfigSetBody = {
|
||||||
requires_restart: 1,
|
requires_restart: 1,
|
||||||
config_data: configData,
|
config_data: configData,
|
||||||
update_topic: `config/cameras/${cameraName}/add`,
|
update_topic: `config/cameras/${finalCameraName}/add`,
|
||||||
};
|
};
|
||||||
|
|
||||||
axios
|
axios
|
||||||
@ -228,8 +230,8 @@ export default function CameraWizardDialog({
|
|||||||
// Use camera name with index suffix for multiple streams
|
// Use camera name with index suffix for multiple streams
|
||||||
const streamName =
|
const streamName =
|
||||||
wizardData.streams!.length === 1
|
wizardData.streams!.length === 1
|
||||||
? cameraName
|
? finalCameraName
|
||||||
: `${cameraName}_${index + 1}`;
|
: `${finalCameraName}_${index + 1}`;
|
||||||
go2rtcStreams[streamName] = [stream.url];
|
go2rtcStreams[streamName] = [stream.url];
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -260,7 +262,7 @@ export default function CameraWizardDialog({
|
|||||||
Promise.allSettled(updatePromises).then(() => {
|
Promise.allSettled(updatePromises).then(() => {
|
||||||
toast.success(
|
toast.success(
|
||||||
t("cameraWizard.save.successWithLive", {
|
t("cameraWizard.save.successWithLive", {
|
||||||
cameraName: wizardData.cameraName,
|
cameraName: friendlyName || finalCameraName,
|
||||||
}),
|
}),
|
||||||
{ position: "top-center" },
|
{ position: "top-center" },
|
||||||
);
|
);
|
||||||
@ -272,7 +274,7 @@ export default function CameraWizardDialog({
|
|||||||
// log the error but don't fail the entire save
|
// log the error but don't fail the entire save
|
||||||
toast.warning(
|
toast.warning(
|
||||||
t("cameraWizard.save.successWithoutLive", {
|
t("cameraWizard.save.successWithoutLive", {
|
||||||
cameraName: wizardData.cameraName,
|
cameraName: friendlyName || finalCameraName,
|
||||||
}),
|
}),
|
||||||
{ position: "top-center" },
|
{ position: "top-center" },
|
||||||
);
|
);
|
||||||
@ -283,7 +285,7 @@ export default function CameraWizardDialog({
|
|||||||
// No valid streams found
|
// No valid streams found
|
||||||
toast.success(
|
toast.success(
|
||||||
t("cameraWizard.save.successWithoutLive", {
|
t("cameraWizard.save.successWithoutLive", {
|
||||||
cameraName: wizardData.cameraName,
|
cameraName: friendlyName || finalCameraName,
|
||||||
}),
|
}),
|
||||||
{ position: "top-center" },
|
{ position: "top-center" },
|
||||||
);
|
);
|
||||||
|
|||||||
47
web/src/utils/cameraUtil.ts
Normal file
47
web/src/utils/cameraUtil.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Generates a fixed-length hash from a camera name for use as a valid camera identifier.
|
||||||
|
* Used when user enters a display name with spaces or special characters.
|
||||||
|
*
|
||||||
|
* @param name - The original camera name/display name
|
||||||
|
* @returns A valid camera identifier (lowercase, alphanumeric, max 8 chars)
|
||||||
|
*/
|
||||||
|
export 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()}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a string is a valid camera name identifier.
|
||||||
|
* Valid camera names contain only letters, numbers, underscores, and hyphens.
|
||||||
|
*
|
||||||
|
* @param name - The camera name to validate
|
||||||
|
* @returns True if the name is valid, false otherwise
|
||||||
|
*/
|
||||||
|
export const isValidCameraName = (name: string): boolean => {
|
||||||
|
return /^[a-zA-Z0-9_-]+$/.test(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a user-entered camera name and returns both the final camera name
|
||||||
|
* and friendly name for Frigate configuration.
|
||||||
|
*
|
||||||
|
* @param userInput - The name entered by the user (could be display name)
|
||||||
|
* @returns Object with finalCameraName and friendlyName
|
||||||
|
*/
|
||||||
|
export const processCameraName = (
|
||||||
|
userInput: string,
|
||||||
|
): {
|
||||||
|
finalCameraName: string;
|
||||||
|
friendlyName?: string;
|
||||||
|
} => {
|
||||||
|
if (isValidCameraName(userInput)) {
|
||||||
|
return { finalCameraName: userInput };
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
finalCameraName: generateFixedHash(userInput),
|
||||||
|
friendlyName: userInput,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user