Procházet zdrojové kódy

half of key value array refactor

Ivan Galakhov před 4 roky
rodič
revize
6753c1314b

+ 10 - 1
dashboard/src/components/form-refactor/PorterForm.tsx

@@ -1,11 +1,18 @@
 import React, { useContext, useState } from "react";
-import { Section, FormField, InputField, CheckboxField } from "./types";
+import {
+  Section,
+  FormField,
+  InputField,
+  CheckboxField,
+  KeyValueArrayField,
+} from "./types";
 import TabRegion, { TabOption } from "../TabRegion";
 import Heading from "../values-form/Heading";
 import Helper from "../values-form/Helper";
 import Input from "./field-components/Input";
 import { PorterFormContext } from "./PorterFormContextProvider";
 import Checkbox from "./field-components/Checkbox";
+import KeyValueArray from "./field-components/KeyValueArray";
 import styled from "styled-components";
 import SaveButton from "../SaveButton";
 
@@ -42,6 +49,8 @@ const PorterForm: React.FC<Props> = (props) => {
         return <Input {...(bundledProps as InputField)} />;
       case "checkbox":
         return <Checkbox {...(bundledProps as CheckboxField)} />;
+      case "key-value-array":
+        return <KeyValueArray {...(bundledProps as KeyValueArrayField)} />;
     }
     return <p>Not Implemented: {(field as any).type}</p>;
   };

+ 4 - 1
dashboard/src/components/form-refactor/PorterFormContextProvider.tsx

@@ -68,7 +68,10 @@ export const PorterFormContextProvider: React.FC<Props> = (props) => {
             ...state.components,
             [action.id]: {
               ...state.components[action.id],
-              state: action.updateFunc(state.components[action.id]),
+              state: {
+                ...state.components[action.id].state,
+                ...action.updateFunc(state.components[action.id].state),
+              },
             },
           },
         };

+ 321 - 0
dashboard/src/components/form-refactor/field-components/KeyValueArray.tsx

@@ -0,0 +1,321 @@
+import React from "react";
+import { KeyValueArrayField, KeyValueArrayFieldState } from "../types";
+import sliders from "../../../assets/sliders.svg";
+import upload from "../../../assets/upload.svg";
+import styled from "styled-components";
+import useFormField from "../hooks/useFormField";
+
+interface Props extends KeyValueArrayField {
+  id: string;
+}
+
+const KeyValueArray: React.FC<Props> = (props) => {
+  const { state, setState, variables } = useFormField<KeyValueArrayFieldState>(
+    props.id,
+    {
+      initState: {
+        values: [],
+        showEnvModal: false,
+        showEditorModal: false,
+      },
+    }
+  );
+
+  console.log(state);
+
+  if (state == undefined) return <></>;
+
+  const renderDeleteButton = (i: number) => {
+    if (!props.isReadOnly) {
+      return (
+        <DeleteButton
+          onClick={() => {
+            state.values.splice(i, 1);
+            setState((prev) => {
+              return {
+                values: prev.values
+                  .slice(0, i + 1)
+                  .concat(prev.values.slice(i + 1, prev.values.length)),
+              };
+            });
+          }}
+        >
+          <i className="material-icons">cancel</i>
+        </DeleteButton>
+      );
+    }
+  };
+
+  const renderHiddenOption = (hidden: boolean, i: number) => {
+    if (props.secretOption && hidden) {
+      return (
+        <HideButton>
+          <i className="material-icons">lock</i>
+        </HideButton>
+      );
+    }
+  };
+
+  const renderInputList = () => {
+    return (
+      <>
+        {state.values.map((entry: any, i: number) => {
+          // Preprocess non-string env values set via raw Helm values
+          let { value } = entry;
+          if (typeof value === "object") {
+            value = JSON.stringify(value);
+          } else if (typeof value === "number" || typeof value === "boolean") {
+            value = value.toString();
+          }
+
+          return (
+            <InputWrapper key={i}>
+              <Input
+                placeholder="ex: key"
+                width="270px"
+                value={entry.key}
+                onChange={(e: any) => {
+                  e.persist();
+                  setState((prev) => {
+                    return {
+                      values: prev.values.map((t, j) => {
+                        if (j == i) {
+                          return {
+                            ...t,
+                            key: e.target.value,
+                          };
+                        }
+                        return t;
+                      }),
+                    };
+                  });
+                }}
+                disabled={props.isReadOnly || value.includes("PORTERSECRET")}
+                spellCheck={false}
+              />
+              <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}
+              />
+              {renderDeleteButton(i)}
+              {renderHiddenOption(value.includes("PORTERSECRET"), i)}
+            </InputWrapper>
+          );
+        })}
+      </>
+    );
+  };
+
+  return (
+    <>
+      <StyledInputArray>
+        <Label>{props.label}</Label>
+        {state.values.length === 0 ? <></> : renderInputList()}
+        {props.isReadOnly ? (
+          <></>
+        ) : (
+          <InputWrapper>
+            <AddRowButton
+              onClick={() => {
+                setState((prev) => {
+                  console.log(prev);
+                  return {
+                    values: [...prev.values, { key: "", value: "" }],
+                  };
+                });
+              }}
+            >
+              <i className="material-icons">add</i> Add Row
+            </AddRowButton>
+            <Spacer />
+            {variables.namespace && props.envLoader && (
+              <LoadButton
+                onClick={() =>
+                  setState((prev) => {
+                    return {
+                      showEnvModal: !prev.showEnvModal,
+                    };
+                  })
+                }
+              >
+                <img src={sliders} /> Load from Env Group
+              </LoadButton>
+            )}
+            {props.fileUpload && (
+              <UploadButton
+                onClick={() => {
+                  setState((prev) => {
+                    return {
+                      showEditorModal: true,
+                    };
+                  });
+                }}
+              >
+                <img src={upload} /> Copy from File
+              </UploadButton>
+            )}
+          </InputWrapper>
+        )}
+      </StyledInputArray>
+      {/*{renderEnvModal()}*/}
+      {/*{renderEditorModal()}*/}
+    </>
+  );
+};
+
+export default KeyValueArray;
+
+const Spacer = styled.div`
+  width: 10px;
+  height: 20px;
+`;
+
+const AddRowButton = styled.div`
+  display: flex;
+  align-items: center;
+  width: 270px;
+  font-size: 13px;
+  color: #aaaabb;
+  height: 32px;
+  border-radius: 3px;
+  cursor: pointer;
+  background: #ffffff11;
+  :hover {
+    background: #ffffff22;
+  }
+
+  > i {
+    color: #ffffff44;
+    font-size: 16px;
+    margin-left: 8px;
+    margin-right: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+`;
+
+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;
+  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 DeleteButton = styled.div`
+  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;
+    }
+  }
+`;
+
+const HideButton = styled(DeleteButton)`
+  margin-top: -5px;
+  > i {
+    font-size: 19px;
+    cursor: default;
+    :hover {
+      color: #ffffff44;
+    }
+  }
+`;
+
+const InputWrapper = styled.div`
+  display: flex;
+  align-items: center;
+  margin-top: 5px;
+`;
+
+const Input = styled.input`
+  outline: none;
+  border: none;
+  margin-bottom: 5px;
+  font-size: 13px;
+  background: #ffffff11;
+  border: 1px solid #ffffff55;
+  border-radius: 3px;
+  width: ${(props: { disabled?: boolean; width: string }) =>
+    props.width ? props.width : "270px"};
+  color: ${(props: { disabled?: boolean; width: string }) =>
+    props.disabled ? "#ffffff44" : "white"};
+  padding: 5px 10px;
+  height: 35px;
+`;
+
+const Label = styled.div`
+  color: #ffffff;
+  margin-bottom: 10px;
+`;
+
+const StyledInputArray = styled.div`
+  margin-bottom: 15px;
+  margin-top: 22px;
+`;

+ 2 - 2
dashboard/src/components/form-refactor/hooks/useFormField.tsx

@@ -9,7 +9,7 @@ import {
 interface FormFieldData<T> {
   state: T;
   variables: PorterFormVariableList;
-  setState: (setFunc: (prev: T) => T) => void;
+  setState: (setFunc: (prev: T) => Partial<T>) => void;
   setVars: (
     setFunc: (vars: PorterFormVariableList) => PorterFormVariableList
   ) => void;
@@ -44,7 +44,7 @@ const useFormField = <T extends PorterFormFieldFieldState>(
     });
   }, []);
 
-  const setState = (updateFunc: (prev: T) => T) => {
+  const setState = (updateFunc: (prev: T) => Partial<T>) => {
     dispatchAction({
       type: "update-field",
       id: fieldId,

+ 21 - 6
dashboard/src/components/form-refactor/types.ts

@@ -15,12 +15,12 @@ export interface GenericInputField extends GenericField {
   variable: string;
 }
 
-export interface HeadingField extends GenericField{
+export interface HeadingField extends GenericField {
   type: "heading";
   label: string;
 }
 
-export interface SubtitleField extends GenericField{
+export interface SubtitleField extends GenericField {
   type: "subtitle";
   label: string;
 }
@@ -45,7 +45,15 @@ export interface CheckboxField extends GenericInputField {
   label?: string;
 }
 
-export type FormField = HeadingField|SubtitleField|InputField|CheckboxField;
+export interface KeyValueArrayField extends GenericInputField {
+  type: "key-value-array";
+  label?: string;
+  secretOption?: boolean;
+  envLoader?: boolean;
+  fileUpload?: boolean;
+}
+
+export type FormField = HeadingField|SubtitleField|InputField|CheckboxField|KeyValueArrayField;
 
 export interface ShowIfAnd {
   and: ShowIf[];
@@ -85,11 +93,18 @@ export interface PorterFormValidationInfo {
 }
 
 // internal field state interfaces
-
 export interface StringInputFieldState {}
 export interface CheckboxFieldState {}
+export interface KeyValueArrayFieldState {
+  values: {
+    key: string,
+    value: string
+  }[];
+  showEnvModal: boolean;
+  showEditorModal: boolean;
+}
 
-export type PorterFormFieldFieldState = StringInputFieldState|CheckboxFieldState;
+export type PorterFormFieldFieldState = StringInputFieldState|CheckboxFieldState|KeyValueArrayField;
 
 // reducer interfaces
 
@@ -122,7 +137,7 @@ export interface PorterFormInitFieldAction {
 export interface PorterFormUpdateFieldAction {
   type: "update-field",
   id: string;
-  updateFunc: (prev: PorterFormFieldFieldState) => PorterFormFieldFieldState;
+  updateFunc: (prev: PorterFormFieldFieldState) => Partial<PorterFormFieldFieldState>;
 }
 
 export interface PorterFormUpdateValidationAction {

+ 1 - 1
dashboard/src/components/values-form/FormDebugger.tsx

@@ -364,7 +364,7 @@ tabs:
       label: Environment Variables
     - type: subtitle
       label: Set environment variables for your secrets and environment-specific configuration.
-    - type: env-key-value-array
+    - type: key-value-array
       label: 
       variable: container.env.normal
 - name: advanced