Просмотр исходного кода

Merge pull request #537 from porter-dev/0.2.0-env-file-upload

[0.2.0] Support .env file upload
abelanger5 5 лет назад
Родитель
Сommit
4d676f48e3

Разница между файлами не показана из-за своего большого размера
+ 1 - 10364
dashboard/package-lock.json


+ 1 - 0
dashboard/package.json

@@ -25,6 +25,7 @@
     "ace-builds": "^1.4.12",
     "anser": "^2.0.1",
     "axios": "^0.20.0",
+    "brace": "^0.11.1",
     "d3-array": "^2.11.0",
     "d3-time-format": "^3.0.0",
     "dotenv": "^8.2.0",

+ 5 - 0
dashboard/src/assets/upload.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.4" d="M18.8088 9.021C18.3573 9.021 17.7592 9.011 17.0146 9.011C15.1987 9.011 13.7055 7.508 13.7055 5.675V2.459C13.7055 2.206 13.5026 2 13.253 2H7.96363C5.49517 2 3.5 4.026 3.5 6.509V17.284C3.5 19.889 5.59022 22 8.16958 22H16.0453C18.5058 22 20.5 19.987 20.5 17.502V9.471C20.5 9.217 20.298 9.012 20.0465 9.013C19.6247 9.016 19.1168 9.021 18.8088 9.021Z" fill="white"/>
+<path opacity="0.4" d="M16.0842 2.56729C15.7852 2.25629 15.2632 2.47029 15.2632 2.90129V5.53829C15.2632 6.64429 16.1742 7.55429 17.2792 7.55429C17.9772 7.56229 18.9452 7.56429 19.7672 7.56229C20.1882 7.56129 20.4022 7.05829 20.1102 6.75429C19.0552 5.65729 17.1662 3.69129 16.0842 2.56729Z" fill="white"/>
+<path d="M15.1052 12.8837C14.8142 13.1727 14.3432 13.1747 14.0512 12.8817L12.4622 11.2847V16.1117C12.4622 16.5227 12.1282 16.8567 11.7172 16.8567C11.3062 16.8567 10.9732 16.5227 10.9732 16.1117V11.2847L9.3822 12.8817C9.0922 13.1747 8.6202 13.1727 8.3292 12.8837C8.0382 12.5947 8.0372 12.1227 8.3272 11.8307L11.1892 8.95569C11.1902 8.95469 11.1902 8.95469 11.1902 8.95469C11.2582 8.88669 11.3402 8.83169 11.4302 8.79469C11.5202 8.75669 11.6182 8.73669 11.7172 8.73669C11.8172 8.73669 11.9152 8.75669 12.0052 8.79469C12.0942 8.83169 12.1752 8.88669 12.2432 8.95369C12.2442 8.95469 12.2452 8.95469 12.2452 8.95569L15.1072 11.8307C15.3972 12.1227 15.3972 12.5947 15.1052 12.8837Z" fill="white"/>
+</svg>

+ 6 - 2
dashboard/src/components/YamlEditor.tsx

@@ -2,8 +2,8 @@ import React, { Component } from "react";
 import styled from "styled-components";
 import AceEditor from "react-ace";
 
+import "shared/ace-porter-theme"
 import "ace-builds/src-noconflict/mode-yaml";
-import "ace-builds/src-noconflict/theme-terminal";
 
 type PropsType = {
   value: string;
@@ -45,7 +45,7 @@ class YamlEditor extends Component<PropsType, StateType> {
           <AceEditor
             mode="yaml"
             value={this.props.value}
-            theme="terminal"
+            theme="porter"
             onChange={this.props.onChange}
             name="codeEditor"
             readOnly={this.props.readOnly}
@@ -53,6 +53,10 @@ class YamlEditor extends Component<PropsType, StateType> {
             height={this.props.height}
             width="100%"
             style={{ borderRadius: "5px" }}
+            showPrintMargin={false}
+            showGutter={true}
+            highlightActiveLine={true}
+            fontSize={14}
           />
         </Editor>
       </Holder>

+ 122 - 21
dashboard/src/components/values-form/KeyValueArray.tsx

@@ -2,8 +2,11 @@ import React, { Component } from "react";
 import styled from "styled-components";
 import Modal from "../../main/home/modals/Modal";
 import LoadEnvGroupModal from "../../main/home/modals/LoadEnvGroupModal";
+import EnvEditorModal from "../../main/home/modals/EnvEditorModal";
 
 import sliders from "assets/sliders.svg";
+import upload from "assets/upload.svg";
+import { keysIn } from "lodash";
 
 type PropsType = {
   label?: string;
@@ -14,17 +17,20 @@ type PropsType = {
   namespace?: string;
   clusterId?: number;
   envLoader?: boolean;
+  fileUpload?: boolean;
 };
 
 type StateType = {
   values: any[];
   showEnvModal: boolean;
+  showEditorModal: boolean;
 };
 
 export default class KeyValueArray extends Component<PropsType, StateType> {
   state = {
     values: [] as any[],
     showEnvModal: false,
+    showEditorModal: false,
   };
 
   componentDidMount() {
@@ -137,6 +143,92 @@ export default class KeyValueArray extends Component<PropsType, StateType> {
     }
   };
 
+  renderEditorModal = () => {
+    if (this.state.showEditorModal) {
+      return (
+        <Modal
+          onRequestClose={() => this.setState({ showEditorModal: false })}
+          width="60%"
+          height="80%"
+        >
+          <EnvEditorModal
+            closeModal={() => this.setState({ showEditorModal: false })}
+            setEnvVariables={(envFile: string) => this.readFile(envFile)}
+          />
+        </Modal>
+      );
+    }
+  };
+
+    // Parses src into an Object
+  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
+  }
+
+  readFile = (env: string) => {
+    let envObj = this.parseEnv(env, null)
+    let push = true;
+
+    for (let key in envObj) {
+      for (var i = 0; i < this.state.values.length; i++) {
+        let existingKey = this.state.values[i]["key"]
+        if (key === existingKey) {
+          this.state.values[i]["value"] = envObj[key]
+          push = false;
+        }
+      }
+
+      if (push) {
+        this.state.values.push({ key, value: envObj[key] });
+      }
+
+    }
+
+    this.setState({ values: this.state.values }, () => {
+      let obj = this.valuesToObject();
+      this.props.setValues(obj);
+    });
+  }
+
   render() {
     return (
       <>
@@ -165,36 +257,25 @@ export default class KeyValueArray extends Component<PropsType, StateType> {
                   <img src={sliders} /> Load from Env Group
                 </LoadButton>
               )}
+              {this.props.fileUpload && (
+                <UploadButton
+                  onClick={()=>{
+                    this.setState({ showEditorModal: true });
+                  }}
+                >
+                  <img src={upload} /> Copy from File
+                </UploadButton>
+              )}
             </InputWrapper>
           )}
         </StyledInputArray>
         {this.renderEnvModal()}
+        {this.renderEditorModal()}
       </>
     );
   }
 }
 
-const CloseOverlay = styled.div`
-  position: fixed;
-  top: 0;
-  left: 0;
-  width: 100vw;
-  height: 100vh;
-  z-index: 999;
-  background: #202227;
-  animation: fadeIn 0.2s 0s;
-  opacity: 0;
-  animation-fill-mode: forwards;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
 const Spacer = styled.div`
   width: 10px;
   height: 20px;
@@ -244,6 +325,26 @@ const LoadButton = styled(AddRowButton)`
   }
 `;
 
+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;

+ 1 - 0
dashboard/src/main/home/cluster-dashboard/env-groups/CreateEnvGroup.tsx

@@ -163,6 +163,7 @@ export default class CreateEnvGroup extends Component<PropsType, StateType> {
             namespace={this.state.selectedNamespace}
             values={this.state.envVariables}
             setValues={(x: any) => this.setState({ envVariables: x })}
+            fileUpload={true}
           />
           <SaveButton
             disabled={this.isDisabled()}

+ 3 - 2
dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroup.tsx

@@ -48,7 +48,7 @@ export default class ExpandedEnvGroup extends Component<PropsType, StateType> {
     values: this.props.envGroup.data as any,
   };
 
-  handleUpdateValues = (config?: any) => {
+  handleUpdateValues = () => {
     let { envGroup } = this.props;
     let name = envGroup.metadata.name;
     let namespace = envGroup.metadata.namespace;
@@ -93,7 +93,8 @@ export default class ExpandedEnvGroup extends Component<PropsType, StateType> {
               <KeyValueArray
                 namespace={namespace}
                 values={this.state.values || {}}
-                setValues={(x: any) => this.setState({ values: x })}
+                setValues={(x: any) => this.setState({ values: x }, () => {console.log(this.state.values)})}
+                fileUpload={true}
               />
             </InnerWrapper>
             <SaveButton

+ 40 - 0
dashboard/src/main/home/integrations/create-integration/GKEForm.tsx

@@ -48,6 +48,32 @@ export default class GKEForm extends Component<PropsType, StateType> {
     // TODO: implement once api is restructured
   };
 
+  // readFile = (env: string) => {
+  //   console.log(env)
+  //   event.preventDefault()
+  //   const reader = new FileReader()
+  //   reader.onload = async (e) => {
+  //     let text = (e.target.result)
+  //     let env = this.parseEnv(text, null)
+
+  //     for (let key in env) {
+  //       // filter duplicate keys
+  //       let dup = this.state.values.filter((el) => {
+  //         console.log(el, key)
+  //         if (el["key"] == key) {
+  //           return false
+  //         }
+  //       })
+
+  //       console.log(dup)
+
+  //       this.state.values.push({ key, value: env[key] });
+  //     }
+  //     this.setState({ values: this.state.values });
+  //   }
+  //   reader.readAsText(event.target.files[0], 'UTF-8')
+  // }
+
   render() {
     return (
       <StyledForm>
@@ -94,6 +120,20 @@ export default class GKEForm extends Component<PropsType, StateType> {
           disabled={this.isDisabled()}
           onClick={this.isDisabled() ? null : this.handleSubmit}
         />
+
+              {/* <UploadButton
+      onClick={()=>{
+        // document.getElementById("file").click();
+        this.setState({ showEditorModal: true });
+      }}
+      >
+      <img src={upload} /> Copy from File
+      {<input id='file' hidden type="file" onChange={(event) => {
+        this.readFile(event)
+        event.currentTarget.value = null
+      }}/>}
+    </UploadButton> */}
+      
       </StyledForm>
     );
   }

+ 166 - 0
dashboard/src/main/home/modals/EnvEditorModal.tsx

@@ -0,0 +1,166 @@
+import React, { Component, createRef } from "react";
+import styled from "styled-components";
+import close from "assets/close.png";
+import AceEditor from "react-ace";
+
+import "shared/ace-porter-theme"
+import "ace-builds/src-noconflict/mode-text";
+
+import { Context } from "shared/Context";
+
+import SaveButton from "components/SaveButton";
+
+type PropsType = {
+  closeModal: () => void;
+  setEnvVariables: (values: any) => void;
+};
+
+type StateType = {
+  error: boolean;
+  buttonStatus: string;
+  envFile: string;
+};
+
+export default class EnvEditorModal extends Component<PropsType, StateType> {
+    state = {
+    error: false,
+    buttonStatus: "",
+    envFile: "",
+  };
+
+  aceEditorRef = React.createRef<AceEditor>();
+
+  onSubmit = () => {
+    this.props.setEnvVariables(this.state.envFile)
+    this.props.closeModal();
+  };
+
+  onChange = (e: string) => { 
+    this.setState({envFile: e})
+  }
+
+  componentDidMount() {}
+
+  render() {
+    return (
+      <StyledLoadEnvGroupModal>
+        <CloseButton onClick={this.props.closeModal}>
+          <CloseButtonImg src={close} />
+        </CloseButton>
+
+        <ModalTitle>Load from Environment Group</ModalTitle>
+        <Subtitle>
+          Copy paste your environment file in .env format:
+        </Subtitle>
+
+        <Editor onSubmit={(e: any) => {e.preventDefault()}} border={true}>
+          <AceEditor
+            ref={this.aceEditorRef}
+            mode="text"
+            value={this.state.envFile}
+            theme="porter"
+            onChange={(e: string) => this.onChange(e)}
+            name="codeEditor"
+            editorProps={{ $blockScrolling: true }}
+            height="100%"
+            width="100%"
+            style={{ borderRadius: "5px" }}
+            showPrintMargin={false}
+            showGutter={true}
+            highlightActiveLine={true}
+            fontSize={14}
+          />
+        </Editor>
+
+        <SaveButton
+          disabled={this.state.envFile == ""}
+          text="Submit"
+          status={
+            this.state.envFile == ""
+              ? "No env file detected"
+              : "Existing env variables will be overidden"
+          }
+          onClick={this.onSubmit}
+        />
+      </StyledLoadEnvGroupModal>
+    );
+  }
+}
+
+EnvEditorModal.contextType = Context;
+
+const Editor = styled.form`
+  margin-top: 20px;
+  border-radius: ${(props: { border: boolean }) => (props.border ? "5px" : "")};
+  border: ${(props: { border: boolean }) =>
+    props.border ? "1px solid #ffffff22" : ""};
+  height: 80%;
+  font-family: monospace !important;
+  .ace_scrollbar {
+    display: none;
+  }
+  .ace_editor,
+  .ace_editor * {
+    font-family: "Monaco", "Menlo", "Ubuntu Mono", "Droid Sans Mono", "Consolas",
+      monospace !important;
+    font-size: 12px !important;
+    font-weight: 400 !important;
+    letter-spacing: 0 !important;
+  }
+`;
+
+const Subtitle = styled.div`
+  margin-top: 15px;
+  font-family: "Work Sans", sans-serif;
+  font-size: 13px;
+  color: #aaaabb;
+`;
+
+const ModalTitle = styled.div`
+  margin: 0px 0px 13px;
+  display: flex;
+  flex: 1;
+  font-family: "Assistant";
+  font-size: 18px;
+  color: #ffffff;
+  user-select: none;
+  font-weight: 700;
+  align-items: center;
+  position: relative;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+`;
+
+const CloseButton = styled.div`
+  position: absolute;
+  display: block;
+  width: 40px;
+  height: 40px;
+  padding: 13px 0 12px 0;
+  z-index: 1;
+  text-align: center;
+  border-radius: 50%;
+  right: 15px;
+  top: 12px;
+  cursor: pointer;
+  :hover {
+    background-color: #ffffff11;
+  }
+`;
+
+const CloseButtonImg = styled.img`
+  width: 14px;
+  margin: 0 auto;
+`;
+
+const StyledLoadEnvGroupModal = styled.div`
+  width: 100%;
+  position: absolute;
+  left: 0;
+  top: 0;
+  height: 100%;
+  padding: 25px 30px;
+  overflow: hidden;
+  border-radius: 6px;
+  background: #202227;
+`;

+ 111 - 0
dashboard/src/shared/ace-porter-theme.js

@@ -0,0 +1,111 @@
+import ace from 'brace';
+
+ace['define']('ace/theme/porter', ['require', 'exports', 'module', 'ace/lib/dom'], (acequire, exports) => {
+    exports.isDark = true;
+    exports.cssClass = "ace-porter";
+    exports.cssText = `.ace-porter, div.ace_content, div.ace_line, div.ace_gutter-cell {\
+    font-family: monospace;
+    font-size: 14px;
+    }
+    .ace-porter {
+    background-color: #1b1d26;
+    }
+    .ace-porter .ace_gutter {
+    background: #1b1d26;
+    color: #929292
+    }
+    .ace-porter .ace_print-margin {
+    width: 1px;
+    background: #1b1d26
+    }
+    .ace-porter .ace_cursor {
+    color: #bfc7d5
+    }
+    .ace-porter .ace_marker-layer .ace_selection {
+    background: #32374D
+    }
+    .ace-porter.ace_multiselect .ace_selection.ace_start {
+    box-shadow: 0 0 3px 0px #191919;
+    }
+    .ace-porter .ace_marker-layer .ace_step {
+    background: rgb(102, 82, 0)
+    }
+    .ace-porter .ace_marker-layer .ace_bracket {
+    margin: -1px 0 0 -1px;
+    border: 1px solid #BFBFBF
+    }
+    .ace-porter .ace_marker-layer .ace_active-line {
+    background: rgba(215, 215, 215, 0.031)
+    }
+    .ace-porter .ace_gutter-active-line {
+    background-color: rgba(215, 215, 215, 0.031)
+    }
+    .ace-porter .ace_marker-layer .ace_selected-word {
+    border: 1px solid #424242
+    }
+    .ace-porter .ace_invisible {
+    color: #343434
+    }
+    .ace-porter .ace_keyword,
+    .ace-porter .ace_meta,
+    .ace-porter .ace_storage,
+    .ace-porter .ace_storage.ace_type,
+    .ace-porter .ace_support.ace_type {
+    color: #ff5572
+    }
+    .ace-porter .ace_keyword.ace_operator {
+    color: #ff5572
+    }
+    .ace-porter .ace_constant.ace_character,
+    .ace-porter .ace_constant.ace_language,
+    .ace-porter .ace_constant.ace_numeric,
+    .ace-porter .ace_keyword.ace_other.ace_unit,
+    .ace-porter .ace_support.ace_constant,
+    .ace-porter .ace_variable.ace_parameter {
+    color: #F78C6C
+    }
+    .ace-porter .ace_constant.ace_other {
+    color: gold
+    }
+    .ace-porter .ace_invalid {
+    color: yellow;
+    background-color: red
+    }
+    .ace-porter .ace_invalid.ace_deprecated {
+    color: #CED2CF;
+    background-color: #B798BF
+    }
+    .ace-porter .ace_fold {
+    background-color: #7AA6DA;
+    border-color: #DEDEDE
+    }
+    .ace-porter .ace_entity.ace_name.ace_function,
+    .ace-porter .ace_support.ace_function,
+    .ace-porter .ace_variable {
+    color: #7AA6DA
+    }
+    .ace-porter .ace_support.ace_class,
+    .ace-porter .ace_support.ace_type {
+    color: #E7C547
+    }
+    .ace-porter .ace_heading,
+    .ace-porter .ace_string {
+    color: #80CBC4
+    }
+    .ace-porter .ace_entity.ace_name.ace_tag,
+    .ace-porter .ace_entity.ace_other.ace_attribute-name,
+    .ace-porter .ace_meta.ace_tag,
+    .ace-porter .ace_string.ace_regexp,
+    .ace-porter .ace_variable {
+    color: #ff5572
+    }
+    .ace-porter .ace_comment {
+    color: #949eff
+    }
+    .ace-porter .ace_indent-guide {
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYLBWV/8PAAK4AYnhiq+xAAAAAElFTkSuQmCC) right repeat-y;
+    }`;
+    
+    var dom = acequire("../lib/dom");
+    dom.importCssString(exports.cssText, exports.cssClass);
+});

Некоторые файлы не были показаны из-за большого количества измененных файлов