From ee2fd13dc4ade75b3ff79a41b15439aa53cc235c Mon Sep 17 00:00:00 2001 From: lorumic Date: Thu, 18 Apr 2024 15:25:39 +0200 Subject: [PATCH] fix(instance): add name length check for instance name (creation/edit) Signed-off-by: lorumic Signed-off-by: lorumic --- src/pages/instances/CreateInstance.tsx | 17 ++------------- src/pages/instances/InstanceDetailHeader.tsx | 20 ++++-------------- src/sass/_rename_header.scss | 8 +++++++ src/util/instances.tsx | 22 ++++++++++++++++++++ 4 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/pages/instances/CreateInstance.tsx b/src/pages/instances/CreateInstance.tsx index 56b969008..9ac5d42d1 100644 --- a/src/pages/instances/CreateInstance.tsx +++ b/src/pages/instances/CreateInstance.tsx @@ -16,7 +16,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { queryKeys } from "util/queryKeys"; import { LxdImageType, RemoteImage } from "types/image"; import { isContainerOnlyImage, isVmOnlyImage, LOCAL_ISO } from "util/images"; -import { checkDuplicateName } from "util/helpers"; import { dump as dumpYaml } from "js-yaml"; import { yamlToObject } from "util/yaml"; import { Link, useLocation, useNavigate, useParams } from "react-router-dom"; @@ -75,6 +74,7 @@ import { import FormFooterLayout from "components/forms/FormFooterLayout"; import { useToastNotification } from "context/toastNotificationProvider"; import { useDocs } from "context/useDocs"; +import { instanceNameValidation } from "util/instances"; export type CreateInstanceFormValues = InstanceDetailsFormValues & FormDeviceValues & @@ -109,20 +109,7 @@ const CreateInstance: FC = () => { } const InstanceSchema = Yup.object().shape({ - name: Yup.string() - .test( - "deduplicate", - "An instance with this name already exists", - (value) => - checkDuplicateName(value, project, controllerState, "instances"), - ) - .matches(/^[A-Za-z0-9-]+$/, { - message: "Only alphanumeric and hyphen characters are allowed", - }) - .matches(/^[A-Za-z].*$/, { - message: "Instance name must start with a letter", - }) - .optional(), + name: instanceNameValidation(project, controllerState).optional(), instanceType: Yup.string().required("Instance type is required"), }); diff --git a/src/pages/instances/InstanceDetailHeader.tsx b/src/pages/instances/InstanceDetailHeader.tsx index ab597f600..c8481e5d8 100644 --- a/src/pages/instances/InstanceDetailHeader.tsx +++ b/src/pages/instances/InstanceDetailHeader.tsx @@ -7,12 +7,12 @@ import { renameInstance } from "api/instances"; import InstanceStateActions from "pages/instances/actions/InstanceStateActions"; import { useFormik } from "formik"; import * as Yup from "yup"; -import { checkDuplicateName } from "util/helpers"; import { useEventQueue } from "context/eventQueue"; import { useToastNotification } from "context/toastNotificationProvider"; import { instanceLinkFromName, instanceLinkFromOperation, + instanceNameValidation, } from "util/instances"; import { getInstanceName } from "util/operations"; import InstanceLink from "pages/instances/InstanceLink"; @@ -30,21 +30,9 @@ const InstanceDetailHeader: FC = ({ name, instance, project }) => { const controllerState = useState(null); const RenameSchema = Yup.object().shape({ - name: Yup.string() - .test( - "deduplicate", - "An instance with this name already exists", - (value) => - instance?.name === value || - checkDuplicateName(value, project, controllerState, "instances"), - ) - .matches(/^[A-Za-z0-9-]+$/, { - message: "Only alphanumeric and hyphen characters are allowed", - }) - .matches(/^[A-Za-z].*$/, { - message: "Instance name must start with a letter", - }) - .required("Instance name is required"), + name: instanceNameValidation(project, controllerState).required( + "Instance name is required", + ), }); const formik = useFormik({ diff --git a/src/sass/_rename_header.scss b/src/sass/_rename_header.scss index c6c8772de..b27766242 100644 --- a/src/sass/_rename_header.scss +++ b/src/sass/_rename_header.scss @@ -40,6 +40,14 @@ .cancel { margin-right: $sph--small; } + + .p-form-validation__message { + font-variant-caps: initial; + font-variant-numeric: initial; + font-weight: initial; + letter-spacing: initial; + text-indent: initial; + } } } diff --git a/src/util/instances.tsx b/src/util/instances.tsx index 4bcf94ea9..1cac666a5 100644 --- a/src/util/instances.tsx +++ b/src/util/instances.tsx @@ -2,6 +2,8 @@ import { LxdOperationResponse } from "types/operation"; import { getInstanceName } from "./operations"; import InstanceLink from "pages/instances/InstanceLink"; import { ReactNode } from "react"; +import { AbortControllerState, checkDuplicateName } from "./helpers"; +import * as Yup from "yup"; export const instanceLinkFromName = (args: { instanceName: string; @@ -24,3 +26,23 @@ export const instanceLinkFromOperation = (args: { } return ; }; + +export const instanceNameValidation = ( + project: string, + controllerState: AbortControllerState, +): Yup.StringSchema => + Yup.string() + .test("deduplicate", "An instance with this name already exists", (value) => + checkDuplicateName(value, project, controllerState, "instances"), + ) + .test( + "size", + "Instance name must be between 1 and 63 characters", + (value) => !value || value.length < 64, + ) + .matches(/^[A-Za-z0-9-]+$/, { + message: "Only alphanumeric and hyphen characters are allowed", + }) + .matches(/^[A-Za-z].*$/, { + message: "Instance name must start with a letter", + });