Răsfoiți Sursa

basic form debugger

jusrhee 5 ani în urmă
părinte
comite
ef60df64a4

BIN
dashboard/src/assets/Light Gradient 08.png


BIN
dashboard/src/assets/gradient.png


+ 125 - 0
dashboard/src/components/values-form/FormDebugger.tsx

@@ -0,0 +1,125 @@
+import React, { Component } from "react";
+import styled from "styled-components";
+import AceEditor from "react-ace";
+import yaml from "js-yaml";
+
+import "shared/ace-porter-theme"
+import "ace-builds/src-noconflict/mode-text";
+
+import Heading from "./Heading";
+import Helper from "./Helper";
+
+type PropsType = {
+  goBack: () => void;
+};
+
+type StateType = {
+  rawYaml: string;
+};
+
+export default class FormDebugger extends Component<PropsType, StateType> {
+  state = {
+    rawYaml: "",
+  }
+
+  renderForm = () => {
+    let formData = yaml.load(this.state.rawYaml);
+    return <h1>silver lining</h1>
+  }
+
+  aceEditorRef = React.createRef<AceEditor>();
+  render() {
+    return (
+      <StyledFormDebugger>
+        <Button onClick={this.props.goBack}>
+          <i className="material-icons">keyboard_backspace</i>
+          Back
+        </Button>
+        <Heading>✨ Form.yaml Editor</Heading>
+        <Helper>Write and test form.yaml free of consequence.</Helper>
+
+        <EditorWrapper>
+          <AceEditor
+            ref={this.aceEditorRef}
+            mode="yaml"
+            value={this.state.rawYaml}
+            theme="porter"
+            onChange={(e: string) => this.setState({ rawYaml: e })}
+            name="codeEditor"
+            editorProps={{ $blockScrolling: true }}
+            height="300px"
+            width="100%"
+            style={{ 
+              borderRadius: "5px", 
+              border: "1px solid #ffffff22",
+              marginTop: "27px"
+            }}
+            showPrintMargin={false}
+            showGutter={true}
+            highlightActiveLine={true}
+          />
+        </EditorWrapper>
+
+        <Heading>🎨 Rendered Form</Heading>
+        {this.renderForm()}
+      </StyledFormDebugger>
+    );
+  }
+}
+
+const EditorWrapper = styled.div`
+  .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 StyledFormDebugger = styled.div`
+  position: relative;
+`;
+
+const Button = styled.div`
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 13px;
+  cursor: pointer;
+  font-family: "Work Sans", sans-serif;
+  border-radius: 20px;
+  color: white;
+  height: 35px;
+  margin-left: -2px;
+  padding: 0px 8px;
+  width: 85px;
+  float: right;
+  padding-bottom: 1px;
+  font-weight: 500;
+  padding-right: 15px;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  cursor: pointer;
+  border: 2px solid #969fbbaa;
+  :hover {
+    background: #ffffff11;
+  }
+
+  > i {
+    color: white;
+    width: 18px;
+    height: 18px;
+    color: #969fbbaa;
+    font-weight: 600;
+    font-size: 14px;
+    border-radius: 20px;
+    display: flex;
+    align-items: center;
+    margin-right: 5px;
+    justify-content: center;
+  }
+`;

+ 181 - 0
dashboard/src/components/values-form/FormWrapper.tsx

@@ -0,0 +1,181 @@
+import React, { Component } from "react";
+import styled from "styled-components";
+
+import { Section, FormElement } from "../../shared/types";
+import { Context } from "../../shared/Context";
+
+import SaveButton from "../SaveButton";
+
+type PropsType = {
+  formTabs: any;
+  onSubmit: (formValues: any) => void;
+  disabled?: boolean;
+  saveValuesStatus?: string | null;
+  isInModal?: boolean;
+  currentTab?: string; // For resetting state when flipping b/w tabs in ExpandedChart
+  renderSaveButton?: boolean;
+  overrideValues?: any;
+};
+
+type StateType = any;
+
+const providerMap: any = {
+  gke: "gcp",
+  eks: "aws",
+  doks: "do",
+};
+
+// Manages the consolidated state of all form tabs ("metastate")
+export default class ValuesWrapper extends Component<PropsType, StateType> {
+  // No need to render, so OK to set as class variable outside of state
+  requiredFields: string[] = [];
+
+  updateFormState() {
+    let metaState: any = {};
+    this.props.formTabs.forEach((tab: any, i: number) => {
+      // TODO: reconcile tab.name and tab.value
+      if (tab.name || (tab.value && tab.value.includes("@"))) {
+        tab.sections.forEach((section: Section, i: number) => {
+          section.contents.forEach((item: FormElement, i: number) => {
+            // If no name is assigned use values.yaml variable as identifier
+            let key = item.name || item.variable;
+
+            let def =
+              item.settings && item.settings.unit
+                ? `${item.settings.default}${item.settings.unit}`
+                : item.settings.default;
+            def = (item.value && item.value[0]) || def;
+
+            if (item.type === "checkbox") {
+              def = item.value[0];
+            }
+
+            // Handle add to list of required fields
+            if (item.required) {
+              key && this.requiredFields.push(key);
+            }
+
+            switch (item.type) {
+              case "checkbox":
+                metaState[key] = def ? def : false;
+                break;
+              case "string-input":
+                metaState[key] = def ? def : "";
+                break;
+              case "string-input-password":
+                metaState[key] = def ? def : item.settings.default;
+              case "array-input":
+                metaState[key] = def ? def : [];
+                break;
+              case "env-key-value-array":
+                metaState[key] = def ? def : {};
+                break;
+              case "key-value-array":
+                metaState[key] = def ? def : {};
+                break;
+              case "number-input":
+                metaState[key] = def.toString() ? def : "";
+                break;
+              case "select":
+                metaState[key] = def ? def : item.settings.options[0].value;
+                break;
+              case "provider-select":
+                def = providerMap[this.context.currentCluster.service];
+                metaState[key] = def ? def : "aws";
+                break;
+              case "base-64":
+                metaState[key] = def ? def : "";
+              case "base-64-password":
+                metaState[key] = def ? def : "";
+              default:
+            }
+          });
+        });
+      }
+    });
+    this.setState(metaState);
+  }
+
+  // Initialize corresponding state fields for form blocks
+  componentDidMount() {
+    this.updateFormState();
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if (
+      this.props.formTabs !== prevProps.formTabs ||
+      this.props.currentTab !== prevProps.currentTab
+    ) {
+      this.updateFormState();
+    }
+    if (this.props.overrideValues !== prevProps.overrideValues) {
+      this.setState({ ...this.props.overrideValues });
+    }
+  }
+
+  // Checks if all required fields are set
+  isDisabled = (): boolean => {
+    let valueIndicators: any[] = [];
+    this.requiredFields.forEach((field: string, i: number) => {
+      valueIndicators.push(this.state[field] && true);
+    });
+    return valueIndicators.includes(false) || valueIndicators.includes("");
+  };
+
+  renderButton = () => {
+    if (this.props.renderSaveButton) {
+      let { formTabs, currentTab } = this.props;
+      let tab = formTabs.find(
+        (t: any) => t.name === currentTab || t.value === currentTab
+      );
+      if (tab && tab.context && tab.context.type === "helm/values") {
+        return (
+          <SaveButton
+            disabled={this.isDisabled() || this.props.disabled}
+            text="Deploy"
+            onClick={() => this.props.onSubmit(this.state)}
+            status={
+              this.isDisabled()
+                ? "Missing required fields"
+                : this.props.saveValuesStatus
+            }
+            makeFlush={true}
+          />
+        );
+      }
+    }
+  };
+
+  render() {
+    let renderFunc: any = this.props.children;
+    if (this.props.isInModal) {
+      return (
+        <StyledValuesWrapper>
+          {renderFunc(this.state, (x: any) => this.setState(x))}
+          {this.renderButton()}
+        </StyledValuesWrapper>
+      );
+    }
+    return (
+      <PaddedWrapper>
+        <StyledValuesWrapper>
+          {renderFunc(this.state, (x: any) => this.setState(x))}
+          {this.renderButton()}
+        </StyledValuesWrapper>
+      </PaddedWrapper>
+    );
+  }
+}
+
+ValuesWrapper.contextType = Context;
+
+const StyledValuesWrapper = styled.div`
+  width: 100%;
+  padding: 0;
+  height: calc(100% - 65px);
+`;
+
+const PaddedWrapper = styled.div`
+  padding-bottom: 65px;
+  position: relative;
+`;

+ 4 - 11
dashboard/src/components/values-form/ValuesForm.tsx

@@ -39,21 +39,13 @@ export default class ValuesForm extends Component<PropsType, StateType> {
     }
     return value;
   };
-
+  
   renderSection = (section: Section) => {
     return section.contents.map((item: FormElement, i: number) => {
+      
       // If no name is assigned use values.yaml variable as identifier
       let key = item.name || item.variable;
 
-      // ugly exception to hide start command option when procfile process is set.
-      if (
-        (item.variable === "container.command" ||
-          (item.type == "subtitle" && item.name == "command_description")) &&
-        this.props.procfileProcess
-      ) {
-        return;
-      }
-
       switch (item.type) {
         case "heading":
           return <Heading key={i}>{item.label}</Heading>;
@@ -277,10 +269,11 @@ export default class ValuesForm extends Component<PropsType, StateType> {
 
   renderFormContents = () => {
     if (this.props.metaState) {
+      console.log(this.props.metaState)
       return this.props.sections.map((section: Section, i: number) => {
         // Hide collapsible section if deciding field is false
         if (section.show_if) {
-          if (!this.props.metaState[section.show_if]) {
+          if (this.props.metaState[section.show_if] === false) {
             return null;
           }
         }

+ 4 - 0
dashboard/src/components/values-form/ValuesWrapper.tsx

@@ -14,6 +14,7 @@ type PropsType = {
   isInModal?: boolean;
   currentTab?: string; // For resetting state when flipping b/w tabs in ExpandedChart
   renderSaveButton?: boolean;
+  overrideValues?: any;
 };
 
 type StateType = any;
@@ -107,6 +108,9 @@ export default class ValuesWrapper extends Component<PropsType, StateType> {
     ) {
       this.updateFormState();
     }
+    if (this.props.overrideValues !== prevProps.overrideValues) {
+      this.setState({ ...this.props.overrideValues });
+    }
   }
 
   // Checks if all required fields are set

+ 1 - 1
dashboard/src/main/home/Home.tsx

@@ -248,7 +248,7 @@ class Home extends Component<PropsType, StateType> {
       return (
         <DashboardWrapper>
           <Placeholder>
-            <Bold>Porter - Getting Started</Bold>
+            <Bold>Porter - Getting</Bold>
             <br />
             <br />
             1. Navigate to{" "}

+ 0 - 2
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -1,9 +1,7 @@
 import React, { Component } from "react";
 import styled from "styled-components";
-import gradient from "assets/gradient.jpg";
 import monojob from "assets/monojob.png";
 import monoweb from "assets/monoweb.png";
-import sliders from "assets/sliders.svg";
 
 import { Context } from "shared/Context";
 import { ChartType, ClusterType } from "shared/types";

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

@@ -1,6 +1,5 @@
 import React, { Component } from "react";
 import styled from "styled-components";
-import yaml from "js-yaml";
 import close from "assets/close.png";
 import key from "assets/key.svg";
 import _ from "lodash";

+ 81 - 37
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -1,7 +1,7 @@
 import React, { Component } from "react";
 import styled from "styled-components";
 
-import gradient from "assets/gradient.jpg";
+import gradient from "assets/gradient.png";
 import { Context } from "shared/Context";
 import { InfraType } from "shared/types";
 import api from "shared/api";
@@ -11,6 +11,7 @@ import ClusterPlaceholderContainer from "./ClusterPlaceholderContainer";
 import { RouteComponentProps, withRouter } from "react-router";
 import TabRegion from "components/TabRegion";
 import Provisioner from "../provisioner/Provisioner";
+import FormDebugger from "components/values-form/FormDebugger";
 
 import { setSearchParam } from "shared/routing";
 
@@ -30,11 +31,17 @@ const tabOptionStrings = ["overview", "create-cluster", "provisioner"];
 
 type StateType = {
   infras: InfraType[];
+  pressingCtrl: boolean;
+  pressingK: boolean;
+  showFormDebugger: boolean;
 };
 
 class Dashboard extends Component<PropsType, StateType> {
   state = {
     infras: [] as InfraType[],
+    pressingCtrl: false,
+    pressingK: false,
+    showFormDebugger: false,
   };
 
   refreshInfras = () => {
@@ -54,8 +61,35 @@ class Dashboard extends Component<PropsType, StateType> {
 
   componentDidMount() {
     this.refreshInfras();
+    document.addEventListener("keydown", this.handleKeyDown);
+    document.addEventListener("keyup", this.handleKeyUp);
   }
 
+  componentWillUnmount() {
+    document.removeEventListener("keydown", this.handleKeyDown);
+    document.removeEventListener("keyup", this.handleKeyUp);
+  }
+
+  handleKeyDown = (e: KeyboardEvent): void => {
+    let { pressingK, pressingCtrl } = this.state;
+    if (e.key === "Meta" || e.key === "Control") {
+      this.setState({ pressingCtrl: true });
+    } 
+    if (e.key === "k") {
+      this.setState({ pressingK: true }); 
+    }
+    if (e.key === "z" && pressingK && pressingCtrl) {
+      this.setState({ pressingK: false, pressingCtrl: false });
+      this.setState({ showFormDebugger: !this.state.showFormDebugger });
+    }
+  };
+
+  handleKeyUp = (e: KeyboardEvent): void => {
+    if (e.key === "Meta" || e.key === "Control" || e.key === "k") {
+      this.setState({ pressingCtrl: false, pressingK: false, });
+    } 
+  };
+
   componentDidUpdate(prevProps: PropsType) {
     if (this.props.projectId && prevProps.projectId !== this.props.projectId) {
       this.refreshInfras();
@@ -101,41 +135,50 @@ class Dashboard extends Component<PropsType, StateType> {
       <>
         {currentProject && (
           <DashboardWrapper>
-            <TitleSection>
-              <DashboardIcon>
-                <DashboardImage src={gradient} />
-                <Overlay>
-                  {currentProject && currentProject.name[0].toUpperCase()}
-                </Overlay>
-              </DashboardIcon>
-              <Title>{currentProject && currentProject.name}</Title>
-              {this.context.currentProject.roles.filter((obj: any) => {
-                return obj.user_id === this.context.user.userId;
-              })[0].kind === "admin" && (
-                <i className="material-icons" onClick={onShowProjectSettings}>
-                  more_vert
-                </i>
-              )}
-            </TitleSection>
-
-            <InfoSection>
-              <TopRow>
-                <InfoLabel>
-                  <i className="material-icons">info</i> Info
-                </InfoLabel>
-              </TopRow>
-              <Description>
-                Project overview for {currentProject && currentProject.name}.
-              </Description>
-            </InfoSection>
-
-            <TabRegion
-              currentTab={this.currentTab()}
-              setCurrentTab={this.setCurrentTab}
-              options={tabOptions}
-            >
-              {this.renderTabContents()}
-            </TabRegion>
+            {
+              this.state.showFormDebugger ? (
+                <FormDebugger 
+                  goBack={() => this.setState({ showFormDebugger: false })} 
+                />
+              ) : (
+                <>
+                  <TitleSection>
+                    <DashboardIcon>
+                      <DashboardImage src={gradient} />
+                      <Overlay>
+                        {currentProject && currentProject.name[0].toUpperCase()}
+                      </Overlay>
+                    </DashboardIcon>
+                    <Title>{currentProject && currentProject.name}</Title>
+                    {this.context.currentProject.roles.filter((obj: any) => {
+                      return obj.user_id === this.context.user.userId;
+                    })[0].kind === "admin" && (
+                      <i className="material-icons" onClick={onShowProjectSettings}>
+                        more_vert
+                      </i>
+                    )}
+                  </TitleSection>
+
+                  <InfoSection>
+                    <TopRow>
+                      <InfoLabel>
+                        <i className="material-icons">info</i> Info
+                      </InfoLabel>
+                    </TopRow>
+                    <Description>
+                      Project overview for {currentProject && currentProject.name}.
+                    </Description>
+                  </InfoSection>
+                  <TabRegion
+                    currentTab={this.currentTab()}
+                    setCurrentTab={this.setCurrentTab}
+                    options={tabOptions}
+                  >
+                    {this.renderTabContents()}
+                  </TabRegion>
+                </>
+              )
+            }
           </DashboardWrapper>
         )}
       </>
@@ -204,7 +247,7 @@ const LineBreak = styled.div`
   width: calc(100% - 0px);
   height: 2px;
   background: #ffffff20;
-  margin: 10px 0px 35px;
+  margin: 10px 0px 20px;
 `;
 
 const Overlay = styled.div`
@@ -228,6 +271,7 @@ const DashboardImage = styled.img`
   height: 45px;
   width: 45px;
   border-radius: 5px;
+  box-shadow: 0 2px 5px 4px #00000011;
 `;
 
 const DashboardIcon = styled.div`

+ 1 - 0
dashboard/src/main/home/launch/expanded-template/ExpandedTemplate.tsx

@@ -55,6 +55,7 @@ export default class ExpandedTemplate extends Component<PropsType, StateType> {
       .then((res) => {
         let { form, values, markdown, metadata } = res.data;
         let keywords = metadata.keywords;
+        console.log(form)
         this.setState({
           form,
           values,

+ 13 - 6
dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx

@@ -58,6 +58,7 @@ type StateType = {
   folderPath: string | null;
   selectedRegistry: any | null;
   env: any;
+  overrideValues: any;
 };
 
 const defaultActionConfig: ActionConfigType = {
@@ -93,6 +94,10 @@ class LaunchTemplate extends Component<PropsType, StateType> {
     folderPath: null as string | null,
     selectedRegistry: null as any | null,
     env: {},
+    overrideValues: {
+      abba: false,
+      tracy: "mcgrady"
+    },
   };
 
   createGHAction = (chartName: string, chartNamespace: string) => {
@@ -426,17 +431,13 @@ class LaunchTemplate extends Component<PropsType, StateType> {
         saveValuesStatus={this.getStatus()}
         disabled={this.submitIsDisabled()}
         renderSaveButton={true}
+        overrideValues={this.state.overrideValues}
       >
         {(metaState: any, setMetaState: any) => {
           if (!metaState) {
             return;
           }
 
-          // handle when procfileProcess is already specified
-          if (this.state.procfileProcess) {
-            metaState["container.command"] = this.state.procfileProcess
-          }
-
           return this.props.form?.tabs.map((tab: any, i: number) => {
             // If tab is current, render
             if (tab.name === this.state.currentTab) {
@@ -697,7 +698,13 @@ class LaunchTemplate extends Component<PropsType, StateType> {
             }
             procfileProcess={this.state.procfileProcess}
             setProcfileProcess={(procfileProcess: string) =>
-              this.setState({ procfileProcess })
+              this.setState({ 
+                procfileProcess, 
+                overrideValues: {
+                  "container.command": procfileProcess || "",
+                  showStartCommand: !procfileProcess
+                } 
+              })
             }
             setBranch={(branch: string) => this.setState({ branch })}
             setDockerfilePath={(x: string) =>

+ 1 - 1
dashboard/src/main/home/modals/EnvEditorModal.tsx

@@ -36,7 +36,7 @@ export default class EnvEditorModal extends Component<PropsType, StateType> {
   };
 
   onChange = (e: string) => { 
-    this.setState({envFile: e})
+    this.setState({ envFile: e })
   }
 
   componentDidMount() {}

+ 0 - 1
dashboard/src/main/home/modals/UpdateClusterModal.tsx

@@ -1,7 +1,6 @@
 import React, { Component } from "react";
 import styled from "styled-components";
 import close from "assets/close.png";
-import gradient from "assets/gradient.jpg";
 
 import api from "shared/api";
 import { Context } from "shared/Context";

+ 1 - 1
dashboard/src/main/home/new-project/NewProject.tsx

@@ -1,7 +1,7 @@
 import React, { Component } from "react";
 import styled from "styled-components";
 
-import gradient from "assets/gradient.jpg";
+import gradient from "assets/gradient.png";
 import { Context } from "shared/Context";
 import { isAlphanumeric } from "shared/common";
 

+ 3 - 1
dashboard/src/main/home/sidebar/ProjectSection.tsx

@@ -1,6 +1,6 @@
 import React, { Component } from "react";
 import styled from "styled-components";
-import gradient from "assets/gradient.jpg";
+import gradient from "assets/gradient.png";
 
 import { Context } from "shared/Context";
 import { ProjectType } from "shared/types";
@@ -210,6 +210,8 @@ const Letter = styled.div`
   height: 100%;
   width: 100%;
   position: absolute;
+  padding-bottom: 2px;
+  font-weight: 500;
   background: #00000028;
   top: 0;
   left: 0;