Sfoglia il codice sorgente

Implemented multi line support for porter form and env groups

jnfrati 4 anni fa
parent
commit
39aab7d082

+ 116 - 109
dashboard/src/components/porter-form/field-components/KeyValueArray.tsx

@@ -20,6 +20,7 @@ import Heading from "components/form-components/Heading";
 import Loading from "components/Loading";
 import api from "shared/api";
 import { Context } from "shared/Context";
+import { dotenv_parse } from "shared/string_utils";
 
 interface Props extends KeyValueArrayField {
   id: string;
@@ -102,51 +103,7 @@ const KeyValueArray: React.FC<Props> = (props) => {
   }
 
   const parseEnv = (src: any, options: any) => {
-    const debug = Boolean(options && options.debug);
-    const obj = {} as Record<string, string>;
-    const NEWLINE = "\n";
-    const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*(.*)?\s*$/;
-    const RE_NEWLINES = /\\n/g;
-    const NEWLINES_MATCH = /\n|\r|\r\n/;
-
-    // convert Buffers before splitting into lines and processing
-    src
-      .toString()
-      .split(NEWLINES_MATCH)
-      .forEach(function (line: any, idx: any) {
-        // matching "KEY' and 'VAL' in 'KEY=VAL'
-        const keyValueArr = line.match(RE_INI_KEY_VAL);
-        // matched?
-        if (keyValueArr != null) {
-          const key = keyValueArr[1];
-          // default undefined or missing values to empty string
-          let val = keyValueArr[2] || "";
-          const end = val.length - 1;
-          const isDoubleQuoted = val[0] === '"' && val[end] === '"';
-          const isSingleQuoted = val[0] === "'" && val[end] === "'";
-
-          // if single or double quoted, remove quotes
-          if (isSingleQuoted || isDoubleQuoted) {
-            val = val.substring(1, end);
-
-            // if double quoted, expand newlines
-            if (isDoubleQuoted) {
-              val = val.replace(RE_NEWLINES, NEWLINE);
-            }
-          } else {
-            // remove surrounding whitespace
-            val = val.trim();
-          }
-
-          obj[key] = val;
-        } else if (debug) {
-          console.log(
-            `did not match key and value when parsing line ${idx + 1}: ${line}`
-          );
-        }
-      });
-
-    return obj;
+    return dotenv_parse(src);
   };
 
   const readFile = (env: string) => {
@@ -333,7 +290,7 @@ const KeyValueArray: React.FC<Props> = (props) => {
 
           return (
             <InputWrapper key={i}>
-              <Input
+              <KeyInput
                 placeholder="ex: key"
                 width="270px"
                 value={entry.key}
@@ -360,30 +317,40 @@ const KeyValueArray: React.FC<Props> = (props) => {
                 }
               />
               <Spacer />
-              <Input
-                placeholder="ex: value"
-                width="270px"
-                value={value}
-                onChange={(e: any) => {
-                  e.persist();
-                  setState((prev) => {
-                    return {
-                      values: prev.values?.map((t, j) => {
-                        if (j == i) {
-                          return {
-                            ...t,
-                            value: e.target.value,
-                          };
-                        }
-                        return t;
-                      }),
-                    };
-                  });
-                }}
-                disabled={props.isReadOnly || value.includes("PORTERSECRET")}
-                type={value.includes("PORTERSECRET") ? "password" : "text"}
-                spellCheck={false}
-              />
+              {value?.includes("PORTERSECRET") ? (
+                <KeyInput
+                  placeholder="ex: value"
+                  width="270px"
+                  disabled
+                  type={"password"}
+                  spellCheck={false}
+                />
+              ) : (
+                <MultiLineInput
+                  placeholder="ex: value"
+                  width="270px"
+                  value={value}
+                  onChange={(e: any) => {
+                    e.persist();
+                    setState((prev) => {
+                      return {
+                        values: prev.values?.map((t, j) => {
+                          if (j == i) {
+                            return {
+                              ...t,
+                              value: e.target.value,
+                            };
+                          }
+                          return t;
+                        }),
+                      };
+                    });
+                  }}
+                  disabled={props.isReadOnly}
+                  spellCheck={false}
+                  rows={value?.split("\n").length}
+                />
+              )}
               {renderDeleteButton(i)}
               {renderHiddenOption(value.includes("PORTERSECRET"), i)}
               {checkOverridedKey(entry.key)}
@@ -486,24 +453,27 @@ export const getFinalVariablesForKeyValueArray: GetFinalVariablesFunction = (
     };
   }
 
+  const isNumber = (s: string) => {
+    return !isNaN(!s ? NaN : Number(String(s).trim()));
+  };
+
+  const rg = /(?:^|[^\\])(\\n)/g;
+  const fixNewlines = (s: string) => {
+    while (rg.test(s)) {
+      s = s.replace(rg, (str) => {
+        if (str.length == 2) return "\n";
+        if (str[0] != "\\") return str[0] + "\n";
+        return "\\n";
+      });
+    }
+    return s;
+  };
+
   if (props.variable.includes("env")) {
     let obj = {
       normal: {},
     } as any;
-    const rg = /(?:^|[^\\])(\\n)/g;
-    const fixNewlines = (s: string) => {
-      while (rg.test(s)) {
-        s = s.replace(rg, (str) => {
-          if (str.length == 2) return "\n";
-          if (str[0] != "\\") return str[0] + "\n";
-          return "\\n";
-        });
-      }
-      return s;
-    };
-    const isNumber = (s: string) => {
-      return !isNaN(!s ? NaN : Number(String(s).trim()));
-    };
+
     state.values.forEach((entry: any, i: number) => {
       if (isNumber(entry.value)) {
         obj.normal[entry.key] = entry.value;
@@ -539,20 +509,7 @@ export const getFinalVariablesForKeyValueArray: GetFinalVariablesFunction = (
     };
   } else {
     let obj = {} as any;
-    const rg = /(?:^|[^\\])(\\n)/g;
-    const fixNewlines = (s: string) => {
-      while (rg.test(s)) {
-        s = s.replace(rg, (str) => {
-          if (str.length == 2) return "\n";
-          if (str[0] != "\\") return str[0] + "\n";
-          return "\\n";
-        });
-      }
-      return s;
-    };
-    const isNumber = (s: string) => {
-      return !isNaN(!s ? NaN : Number(String(s).trim()));
-    };
+
     state.values.forEach((entry: any, i: number) => {
       if (isNumber(entry.value)) {
         obj[entry.key] = entry.value;
@@ -654,22 +611,34 @@ const ExpandableEnvGroup: React.FC<{
 
                     return (
                       <InputWrapper key={i}>
-                        <Input
+                        <KeyInput
                           placeholder="ex: key"
                           width="270px"
                           value={key}
                           disabled
                         />
                         <Spacer />
-                        <Input
-                          placeholder="ex: value"
-                          width="270px"
-                          value={value}
-                          disabled
-                          type={
-                            value.includes("PORTERSECRET") ? "password" : "text"
-                          }
-                        />
+                        {value?.includes("PORTERSECRET") ? (
+                          <KeyInput
+                            placeholder="ex: value"
+                            width="270px"
+                            value={value}
+                            disabled
+                            type={
+                              value.includes("PORTERSECRET")
+                                ? "password"
+                                : "text"
+                            }
+                          />
+                        ) : (
+                          <MultiLineInput
+                            placeholder="ex: value"
+                            width="270px"
+                            value={value}
+                            disabled
+                            rows={value?.split("\n").length}
+                          ></MultiLineInput>
+                        )}
                       </InputWrapper>
                     );
                   }
@@ -822,7 +791,7 @@ type InputProps = {
   borderColor?: string;
 };
 
-const Input = styled.input<InputProps>`
+const KeyInput = styled.input<InputProps>`
   outline: none;
   border: none;
   margin-bottom: 5px;
@@ -837,6 +806,44 @@ const Input = styled.input<InputProps>`
   height: 35px;
 `;
 
+export const MultiLineInput = styled.textarea<InputProps>`
+  outline: none;
+  border: none;
+  margin-bottom: 5px;
+  font-size: 13px;
+  background: #ffffff11;
+  border: 1px solid
+    ${(props) => (props.borderColor ? props.borderColor : "#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: 5px 10px;
+  min-height: 35px;
+  max-height: 100px;
+
+  ::-webkit-scrollbar {
+    width: 8px;
+  }
+
+  ::-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-thumb {
+    background-color: darkgrey;
+    outline: 1px solid slategrey;
+  }
+`;
+
 const Label = styled.div`
   color: #ffffff;
   margin-bottom: 10px;

+ 33 - 34
dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupArray.tsx

@@ -4,7 +4,8 @@ import Modal from "main/home/modals/Modal";
 import EnvEditorModal from "main/home/modals/EnvEditorModal";
 
 import upload from "assets/upload.svg";
-import { parseStringToEnvObject } from "./utils";
+import { MultiLineInput } from "components/porter-form/field-components/KeyValueArray";
+import { dotenv_parse } from "shared/string_utils";
 
 export type KeyValueType = {
   key: string;
@@ -44,7 +45,7 @@ const EnvGroupArray = ({
   }, [values]);
 
   const readFile = (env: string) => {
-    const envObj = parseStringToEnvObject(env, null);
+    const envObj = dotenv_parse(env);
     const _values = values;
 
     for (const key in envObj) {
@@ -99,19 +100,36 @@ const EnvGroupArray = ({
                     spellCheck={false}
                   />
                   <Spacer />
-                  <Input
-                    placeholder="ex: value"
-                    width="270px"
-                    value={entry.value}
-                    onChange={(e: any) => {
-                      let _values = values;
-                      _values[i].value = e.target.value;
-                      setValues(_values);
-                    }}
-                    disabled={disabled || entry.locked}
-                    type={entry.hidden ? "password" : "text"}
-                    spellCheck={false}
-                  />
+
+                  {entry.hidden ? (
+                    <Input
+                      placeholder="ex: value"
+                      width="270px"
+                      value={entry.value}
+                      onChange={(e: any) => {
+                        let _values = values;
+                        _values[i].value = e.target.value;
+                        setValues(_values);
+                      }}
+                      disabled={disabled || entry.locked}
+                      type={entry.hidden ? "password" : "text"}
+                      spellCheck={false}
+                    />
+                  ) : (
+                    <MultiLineInput
+                      placeholder="ex: value"
+                      width="270px"
+                      value={entry.value}
+                      onChange={(e: any) => {
+                        let _values = values;
+                        _values[i].value = e.target.value;
+                        setValues(_values);
+                      }}
+                      rows={entry.value?.split("\n").length}
+                      disabled={disabled || entry.locked}
+                      spellCheck={false}
+                    />
+                  )}
 
                   {secretOption && (
                     <HideButton
@@ -227,25 +245,6 @@ const AddRowButton = styled.div`
   }
 `;
 
-const LoadButton = styled(AddRowButton)`
-  background: none;
-  border: 1px solid #ffffff55;
-  > i {
-    color: #ffffff44;
-    font-size: 16px;
-    margin-left: 8px;
-    margin-right: 10px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-  }
-  > img {
-    width: 14px;
-    margin-left: 10px;
-    margin-right: 12px;
-  }
-`;
-
 const UploadButton = styled(AddRowButton)`
   background: none;
   position: relative;

+ 0 - 49
dashboard/src/main/home/cluster-dashboard/env-groups/utils.ts

@@ -1,49 +0,0 @@
-export const parseStringToEnvObject = (src: any, options: any) => {
-  const debug = Boolean(options && options.debug);
-  const obj = {} as Record<string, string>;
-  const NEWLINE = "\n";
-  const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*(.*)?\s*$/;
-  const RE_NEWLINES = /\\n/g;
-  const NEWLINES_MATCH = /\n|\r|\r\n/;
-
-  // convert Buffers before splitting into lines and processing
-  src
-    .toString()
-    .split(NEWLINES_MATCH)
-    .forEach(function (line: any, idx: any) {
-      // matching "KEY' and 'VAL' in 'KEY=VAL'
-      const keyValueArr = line.match(RE_INI_KEY_VAL);
-      // matched?
-      if (keyValueArr != null) {
-        const key = keyValueArr[1];
-        // default undefined or missing values to empty string
-        let val = keyValueArr[2] || "";
-        const end = val.length - 1;
-        const isDoubleQuoted = val[0] === '"' && val[end] === '"';
-        const isSingleQuoted = val[0] === "'" && val[end] === "'";
-
-        // if single or double quoted, remove quotes
-        if (isSingleQuoted || isDoubleQuoted) {
-          val = val.substring(1, end);
-
-          // if double quoted, expand newlines
-          if (isDoubleQuoted) {
-            val = val.replace(RE_NEWLINES, NEWLINE);
-          }
-        } else {
-          // remove surrounding whitespace
-          val = val.trim();
-        }
-
-        obj[key] = val;
-      } else if (debug) {
-        /*
-        console.log(
-          `did not match key and value when parsing line ${idx + 1}: ${line}`
-        );
-        */
-      }
-    });
-
-  return obj;
-};

+ 42 - 0
dashboard/src/shared/string_utils.ts

@@ -11,3 +11,45 @@ export const readableDate = (s: string) => {
 export const capitalize = (s: string) => {
   return s.charAt(0).toUpperCase() + s.substring(1).toLowerCase();
 };
+
+const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
+
+export const dotenv_parse = (src: string): Record<string, string> => {
+  // Parser src into an Object
+
+  const obj = {} as Record<string, string>;
+
+  // Convert buffer to string
+  let lines = src.toString();
+
+  // Convert line breaks to same format
+  lines = lines.replace(/\r\n?/gm, "\n");
+
+  let match;
+  while ((match = LINE.exec(lines)) != null) {
+    const key = match[1];
+
+    // Default undefined or null to empty string
+    let value = match[2] || "";
+
+    // Remove whitespace
+    value = value.trim();
+
+    // Check if double quoted
+    const maybeQuote = value[0];
+
+    // Remove surrounding quotes
+    value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
+
+    // Expand newlines if double quoted
+    if (maybeQuote === '"') {
+      value = value.replace(/\\n/g, "\n");
+      value = value.replace(/\\r/g, "\r");
+    }
+
+    // Add to object
+    obj[key] = value;
+  }
+
+  return obj;
+};