瀏覽代碼

Prevent Number from leading Env Var (#3958)

sdess09 2 年之前
父節點
當前提交
6d3f9b2661

+ 111 - 82
dashboard/src/main/home/app-dashboard/validate-apply/app-settings/EnvVarRow.tsx

@@ -1,8 +1,8 @@
 import React from "react";
-import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
+import { type KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
 import Tooltip from "components/porter/Tooltip";
 import { Controller, useFormContext } from "react-hook-form";
-import { PorterAppFormData } from "lib/porter-apps";
+import { type PorterAppFormData } from "lib/porter-apps";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
 import styled from "styled-components";
@@ -12,15 +12,31 @@ type Props = {
     index: number;
     remove: () => void;
     isKeyOverriding: (key: string) => boolean;
+    invalidKey: (key: string) => boolean;
 }
 const EnvVarRow: React.FC<Props> = ({
     entry,
     index,
     remove,
     isKeyOverriding,
+    invalidKey,
 }) => {
-    const { control: appControl, watch } = useFormContext<PorterAppFormData>();
+    const { control: appControl, watch, setError, clearErrors, formState: { errors } } = useFormContext<PorterAppFormData>();
     const hidden = watch(`app.env.${index}.hidden`);
+    const keys = watch(`app.env.${index}.key`);
+
+    const validateKey = (key: string): boolean => {
+        const isValid = /^[A-Za-z]/.test(key);
+        if (!isValid) {
+            setError(`app.env.${index}.key`, {
+                type: "manual",
+                message: "Key must begin with a letter",
+            });
+        } else {
+            clearErrors(`app.env.${index}.key`);
+        }
+        return isValid;
+    };
 
     return (
         <InputWrapper>
@@ -42,15 +58,21 @@ const EnvVarRow: React.FC<Props> = ({
                 <Controller
                     name={`app.env.${index}.key`}
                     control={appControl}
-                    render={({ field: { value, onChange } }) => (
-                        <Input
-                            placeholder="ex: key"
-                            width="270px"
-                            value={value}
-                            onChange={(e) => onChange(e.target.value)}
-                            spellCheck={false}
-                            override={isKeyOverriding(value)}
-                        />
+                    render={({ field: { value, onChange }, fieldState: { error } }) => (
+                        <>
+                            <Input
+                                placeholder="ex: key"
+                                width="270px"
+                                value={value}
+                                onChange={(e) => {
+                                    validateKey(e.target.value);
+                                    onChange(e.target.value);
+                                }}
+                                spellCheck={false}
+                                override={isKeyOverriding(value)}
+                                style={error ? { borderColor: '#fbc902' } : {}}
+                            />
+                        </>
                     )}
                 />
             )}
@@ -80,7 +102,7 @@ const EnvVarRow: React.FC<Props> = ({
                                 placeholder="ex: value"
                                 width="270px"
                                 value={value}
-                                onChange={(e) => onChange(e.target.value)}
+                                onChange={(e) => { onChange(e.target.value); }}
                                 type={"password"}
                                 spellCheck={false}
                                 override={isKeyOverriding(entry.key)}
@@ -97,7 +119,7 @@ const EnvVarRow: React.FC<Props> = ({
                             placeholder="ex: value"
                             width="270px"
                             value={value}
-                            onChange={(e) => onChange(e.target.value)}
+                            onChange={(e) => { onChange(e.target.value); }}
                             rows={value?.split("\n").length}
                             spellCheck={false}
                             override={isKeyOverriding(entry.key)}
@@ -148,6 +170,12 @@ const EnvVarRow: React.FC<Props> = ({
             >
                 <i className="material-icons">cancel</i>
             </DeleteButton>
+            {!invalidKey(keys) && (
+                <>
+                    <Spacer x={1} inline />
+                    <Text color={'#fbc902'}>Key must begin with a letter</Text>
+                </>
+            )}
             {isKeyOverriding(entry.key) && (
                 <>
                     <Spacer x={1} inline />
@@ -159,12 +187,13 @@ const EnvVarRow: React.FC<Props> = ({
 };
 export default EnvVarRow;
 
+
 const InputWrapper = styled.div`
-  display: flex;
-  align-items: center;
-  margin-top: 5px;
+            display: flex;
+            align-items: center;
+            margin-top: 5px;
 
-`;
+            `;
 
 type InputProps = {
     disabled?: boolean;
@@ -173,91 +202,91 @@ type InputProps = {
 };
 
 const Input = styled.input<InputProps>`
-    outline: none;
-    border: none;
-    margin-bottom: 5px;
-    font-size: 13px;
-    background: #ffffff11;
-    border: ${(props) => (props.override ? '2px solid #6b74d6' : ' 1px solid #ffffff55')};
-    border-radius: 3px;
-    width: ${(props) => props.width ? props.width : "270px"};
-    color: ${(props) => props.disabled ? "#ffffff44" : "white"};
-    padding: 5px 10px;
-    height: 35px;
-  `;
+                outline: none;
+                border: none;
+                margin-bottom: 5px;
+                font-size: 13px;
+                background: #ffffff11;
+                border: ${(props) => (props.override ? '2px solid #6b74d6' : ' 1px solid #ffffff55')};
+                border-radius: 3px;
+                width: ${(props) => props.width ? props.width : "270px"};
+                color: ${(props) => props.disabled ? "#ffffff44" : "white"};
+                padding: 5px 10px;
+                height: 35px;
+                `;
 
 const MultiLineInputer = styled.textarea<InputProps>`
-  outline: none;
-  border: none;
-  margin-bottom: 5px;
-  font-size: 13px;
-  background: #ffffff11;
-  border: ${(props) => (props.override ? '2px solid #6b74d6' : ' 1px solid #ffffff55')};
-  border-radius: 3px;
-  min-width: ${(props) => (props.width ? props.width : "270px")};
-  max-width: ${(props) => (props.width ? props.width : "270px")};
-  color: ${(props) => (props.disabled ? "#ffffff44" : "white")};
-  padding: 8px 10px 5px 10px;
-  min-height: 35px;
-  max-height: 100px;
-  white-space: nowrap;
+                    outline: none;
+                    border: none;
+                    margin-bottom: 5px;
+                    font-size: 13px;
+                    background: #ffffff11;
+                    border: ${(props) => (props.override ? '2px solid #6b74d6' : ' 1px solid #ffffff55')};
+                    border-radius: 3px;
+                    min-width: ${(props) => (props.width ? props.width : "270px")};
+                    max-width: ${(props) => (props.width ? props.width : "270px")};
+                    color: ${(props) => (props.disabled ? "#ffffff44" : "white")};
+                    padding: 8px 10px 5px 10px;
+                    min-height: 35px;
+                    max-height: 100px;
+                    white-space: nowrap;
 
-  ::-webkit-scrollbar {
-    width: 8px;
-    :horizontal {
-      height: 8px;
+                    ::-webkit-scrollbar {
+                        width: 8px;
+                    :horizontal {
+                        height: 8px;
     }
   }
 
-  ::-webkit-scrollbar-corner {
-    width: 10px;
-    background: #ffffff11;
-    color: white;
+                    ::-webkit-scrollbar-corner {
+                        width: 10px;
+                    background: #ffffff11;
+                    color: white;
   }
 
-  ::-webkit-scrollbar-track {
-    width: 10px;
-    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
-    box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+                    ::-webkit-scrollbar-track {
+                        width: 10px;
+                    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+                    box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
   }
 
-  ::-webkit-scrollbar-thumb {
-    background-color: darkgrey;
-    outline: 1px solid slategrey;
+                    ::-webkit-scrollbar-thumb {
+                        background - color: darkgrey;
+                    outline: 1px solid slategrey;
   }
-`;
+                    `;
 
 const DeleteButton = styled.div`
-  width: 15px;
-  height: 15px;
-  display: flex;
-  align-items: center;
-  margin-left: 8px;
-  margin-top: -3px;
-  justify-content: center;
+                    width: 15px;
+                    height: 15px;
+                    display: flex;
+                    align-items: center;
+                    margin-left: 8px;
+                    margin-top: -3px;
+                    justify-content: center;
 
   > i {
-    font-size: 17px;
-    color: #ffffff44;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    cursor: pointer;
-    :hover {
-      color: #ffffff88;
+                        font - size: 17px;
+                    color: #ffffff44;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    cursor: pointer;
+                    :hover {
+                        color: #ffffff88;
     }
   }
-`;
+                    `;
 
 const HideButton = styled(DeleteButton)`
-  margin-top: -5px;
+                    margin-top: -5px;
   > i {
-    font-size: 19px;
-    cursor: ${(props: { disabled: boolean }) =>
+                        font - size: 19px;
+                    cursor: ${(props: { disabled: boolean }) =>
         props.disabled ? "default" : "pointer"};
-    :hover {
-      color: ${(props: { disabled: boolean }) =>
+                    :hover {
+                        color: ${(props: { disabled: boolean }) =>
         props.disabled ? "#ffffff44" : "#ffffff88"};
     }
   }
-`;
+                    `;

+ 22 - 14
dashboard/src/main/home/app-dashboard/validate-apply/app-settings/EnvVariables.tsx

@@ -5,9 +5,9 @@ import EnvEditorModal from "main/home/modals/EnvEditorModal";
 
 import upload from "assets/upload.svg";
 import { dotenv_parse } from "shared/string_utils";
-import { NewPopulatedEnvGroup } from "components/porter-form/types";
+import { type NewPopulatedEnvGroup } from "components/porter-form/types";
 import Spacer from "components/porter/Spacer";
-import { PorterAppFormData } from "lib/porter-apps";
+import { type PorterAppFormData } from "lib/porter-apps";
 import { useFormContext, useFieldArray } from "react-hook-form";
 import EnvVarRow from "./EnvVarRow";
 
@@ -35,13 +35,20 @@ const EnvVariables = ({
     name: "app.env",
   });
 
-  const isKeyOverriding = (key: string) => {
+  const isKeyOverriding = (key: string): boolean => {
     if (!syncedEnvGroups) return false;
     return syncedEnvGroups.some(envGroup =>
       key in envGroup.variables || key in envGroup?.secret_variables
     );
   };
 
+  const invalidKey = (key: string): boolean => {
+    const isValid = /^[A-Za-z]/.test(key);
+
+    return isValid;
+  };
+
+
   const readFile = (env: string) => {
     const envObj = dotenv_parse(env);
     for (const key in envObj) {
@@ -69,8 +76,9 @@ const EnvVariables = ({
             key={entry.id}
             entry={entry}
             index={i}
-            remove={() => remove(i)}
+            remove={() => { remove(i); }}
             isKeyOverriding={isKeyOverriding}
+            invalidKey={invalidKey}
           />
         )}
         <InputWrapper>
@@ -88,24 +96,24 @@ const EnvVariables = ({
             <i className="material-icons">add</i> Add Row
           </AddRowButton>
           <Spacer x={0.5} inline />
-            <UploadButton
-              onClick={() => {
-                setShowEditorModal(true);
-              }}
-            >
-              <img src={upload} alt="Upload" /> Copy from File
-            </UploadButton>
+          <UploadButton
+            onClick={() => {
+              setShowEditorModal(true);
+            }}
+          >
+            <img src={upload} alt="Upload" /> Copy from File
+          </UploadButton>
         </InputWrapper>
       </StyledInputArray>
       {showEditorModal && (
         <Modal
-          onRequestClose={() => setShowEditorModal(false)}
+          onRequestClose={() => { setShowEditorModal(false); }}
           width="60%"
           height="650px"
         >
           <EnvEditorModal
-            closeModal={() => setShowEditorModal(false)}
-            setEnvVariables={(envFile: string) => readFile(envFile)}
+            closeModal={() => { setShowEditorModal(false); }}
+            setEnvVariables={(envFile: string) => { readFile(envFile); }}
           />
         </Modal>
       )}