mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-12 16:16:42 +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 { FrigateConfig } from "@/types/frigateConfig";
|
||||
import useSWR from "swr";
|
||||
import { processCameraName } from "@/utils/cameraUtil";
|
||||
|
||||
type ConfigSetBody = {
|
||||
requires_restart: number;
|
||||
@ -30,12 +31,6 @@ 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>;
|
||||
@ -168,19 +163,15 @@ export default function CameraEditForm({
|
||||
|
||||
const saveCameraConfig = (values: FormValues) => {
|
||||
setIsLoading(true);
|
||||
let finalCameraName = values.cameraName;
|
||||
let friendly_name: string | undefined = undefined;
|
||||
const isValidName = /^[a-zA-Z0-9_-]+$/.test(values.cameraName);
|
||||
if (!isValidName) {
|
||||
finalCameraName = generateFixedHash(finalCameraName);
|
||||
friendly_name = values.cameraName;
|
||||
}
|
||||
const { finalCameraName, friendlyName } = processCameraName(
|
||||
values.cameraName,
|
||||
);
|
||||
|
||||
const configData: ConfigSetBody["config_data"] = {
|
||||
cameras: {
|
||||
[finalCameraName]: {
|
||||
enabled: values.enabled,
|
||||
...(friendly_name && { friendly_name }),
|
||||
...(friendlyName && { friendly_name: friendlyName }),
|
||||
ffmpeg: {
|
||||
inputs: values.ffmpeg.inputs.map((input) => ({
|
||||
path: input.path,
|
||||
|
||||
@ -19,6 +19,7 @@ import type {
|
||||
CameraConfigData,
|
||||
ConfigSetBody,
|
||||
} from "@/types/cameraWizard";
|
||||
import { processCameraName } from "@/utils/cameraUtil";
|
||||
|
||||
type WizardState = {
|
||||
wizardData: Partial<WizardFormData>;
|
||||
@ -158,12 +159,17 @@ export default function CameraWizardDialog({
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
// Process camera name and friendly name
|
||||
const { finalCameraName, friendlyName } = processCameraName(
|
||||
wizardData.cameraName,
|
||||
);
|
||||
|
||||
// Convert wizard data to Frigate config format
|
||||
const cameraName = wizardData.cameraName;
|
||||
const configData: CameraConfigData = {
|
||||
cameras: {
|
||||
[cameraName]: {
|
||||
[finalCameraName]: {
|
||||
enabled: true,
|
||||
...(friendlyName && { friendly_name: friendlyName }),
|
||||
ffmpeg: {
|
||||
inputs: wizardData.streams.map((stream, index) => {
|
||||
const isRestreamed =
|
||||
@ -171,8 +177,8 @@ export default function CameraWizardDialog({
|
||||
if (isRestreamed) {
|
||||
const go2rtcStreamName =
|
||||
wizardData.streams!.length === 1
|
||||
? cameraName
|
||||
: `${cameraName}_${index + 1}`;
|
||||
? finalCameraName
|
||||
: `${finalCameraName}_${index + 1}`;
|
||||
return {
|
||||
path: `rtsp://127.0.0.1:8554/${go2rtcStreamName}`,
|
||||
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
|
||||
if (wizardData.streams && wizardData.streams.length > 0) {
|
||||
configData.cameras[cameraName].live = {
|
||||
configData.cameras[finalCameraName].live = {
|
||||
streams: {},
|
||||
};
|
||||
wizardData.streams.forEach((_, index) => {
|
||||
const go2rtcStreamName =
|
||||
wizardData.streams!.length === 1
|
||||
? cameraName
|
||||
: `${cameraName}_${index + 1}`;
|
||||
configData.cameras[cameraName].live!.streams[`Stream ${index + 1}`] =
|
||||
go2rtcStreamName;
|
||||
? finalCameraName
|
||||
: `${finalCameraName}_${index + 1}`;
|
||||
configData.cameras[finalCameraName].live!.streams[
|
||||
`Stream ${index + 1}`
|
||||
] = go2rtcStreamName;
|
||||
});
|
||||
}
|
||||
|
||||
const requestBody: ConfigSetBody = {
|
||||
requires_restart: 1,
|
||||
config_data: configData,
|
||||
update_topic: `config/cameras/${cameraName}/add`,
|
||||
update_topic: `config/cameras/${finalCameraName}/add`,
|
||||
};
|
||||
|
||||
axios
|
||||
@ -228,8 +230,8 @@ export default function CameraWizardDialog({
|
||||
// Use camera name with index suffix for multiple streams
|
||||
const streamName =
|
||||
wizardData.streams!.length === 1
|
||||
? cameraName
|
||||
: `${cameraName}_${index + 1}`;
|
||||
? finalCameraName
|
||||
: `${finalCameraName}_${index + 1}`;
|
||||
go2rtcStreams[streamName] = [stream.url];
|
||||
});
|
||||
|
||||
@ -260,7 +262,7 @@ export default function CameraWizardDialog({
|
||||
Promise.allSettled(updatePromises).then(() => {
|
||||
toast.success(
|
||||
t("cameraWizard.save.successWithLive", {
|
||||
cameraName: wizardData.cameraName,
|
||||
cameraName: friendlyName || finalCameraName,
|
||||
}),
|
||||
{ position: "top-center" },
|
||||
);
|
||||
@ -272,7 +274,7 @@ export default function CameraWizardDialog({
|
||||
// log the error but don't fail the entire save
|
||||
toast.warning(
|
||||
t("cameraWizard.save.successWithoutLive", {
|
||||
cameraName: wizardData.cameraName,
|
||||
cameraName: friendlyName || finalCameraName,
|
||||
}),
|
||||
{ position: "top-center" },
|
||||
);
|
||||
@ -283,7 +285,7 @@ export default function CameraWizardDialog({
|
||||
// No valid streams found
|
||||
toast.success(
|
||||
t("cameraWizard.save.successWithoutLive", {
|
||||
cameraName: wizardData.cameraName,
|
||||
cameraName: friendlyName || finalCameraName,
|
||||
}),
|
||||
{ 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