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

integrate refactored form with launch flow and expanded chart

jusrhee 4 лет назад
Родитель
Сommit
a97d92a41b

+ 67 - 17
dashboard/src/components/form-refactor/PorterForm.tsx

@@ -33,6 +33,12 @@ interface Props {
     submitValues?: any
   ) => React.ReactElement;
   saveButtonText?: string;
+  isReadOnly?: boolean;
+  isInModal?: boolean;
+  color?: string;
+  addendum?: any;
+  saveValuesStatus?: string;
+  externalValues?: any;
 }
 
 const PorterForm: React.FC<Props> = (props) => {
@@ -41,7 +47,9 @@ const PorterForm: React.FC<Props> = (props) => {
   );
 
   const [currentTab, setCurrentTab] = useState(
-    formData.tabs.length > 0 ? formData.tabs[0].name : ""
+    props.leftTabOptions?.length > 0 ? props.leftTabOptions[0].value : (
+      formData.tabs.length > 0 ? formData.tabs[0].name : ""
+    )
   );
 
   const renderSectionField = (field: FormField): JSX.Element => {
@@ -98,16 +106,36 @@ const PorterForm: React.FC<Props> = (props) => {
       .concat(props.rightTabOptions || []);
   };
 
-  const renderTab = (name: string): JSX.Element => {
-    const tab = formData.tabs.filter((tab) => tab.name == name)[0];
+  const showSaveButton = (): boolean => {
+    if (props.isReadOnly) {
+      return false;
+    }
+
+    let returnVal = true;
+    props.leftTabOptions?.forEach((tab: any) => {
+      if (tab.value === currentTab) {
+        returnVal = false;
+      }
+    });
+    props.rightTabOptions?.forEach((tab: any) => {
+      if (tab.value === currentTab) {
+        returnVal = false;
+      }
+    });
+
+    return returnVal;
+  };
 
+  const renderTab = (): JSX.Element => {
+    const tab = formData.tabs.filter((tab) => tab.name == currentTab)[0];
+
+    // Handle external tab
     if (!tab) {
-      // tab is external
-      return props.renderTabContents ? props.renderTabContents(name) : <></>;
+      return props.renderTabContents ? props.renderTabContents(currentTab) : <></>;
     }
 
     return (
-      <>
+      <StyledPorterForm showSave={showSaveButton()}>
         {tab.sections.map((section) => {
           return (
             <React.Fragment key={section.name}>
@@ -115,26 +143,48 @@ const PorterForm: React.FC<Props> = (props) => {
             </React.Fragment>
           );
         })}
-      </>
+      </StyledPorterForm>
     );
   };
 
+  const isDisabled = () => {
+    if (props.saveValuesStatus == "loading") {
+      return true;
+    }
+
+    return isReadOnly || !validationInfo.validated;
+  };
+
+  const renderSaveStatus = (): string => {
+    if (isDisabled() && props.saveValuesStatus !== "loading") {
+      return "Missing required fields";
+    }
+    return props.saveValuesStatus;
+  }
+
   return (
     <>
       <TabRegion
+        addendum={props.addendum}
+        color={props.color}
         options={getTabOptions()}
         currentTab={currentTab}
         setCurrentTab={setCurrentTab}
       >
-        <StyledPorterForm>{renderTab(currentTab)}</StyledPorterForm>
+        {renderTab()}
       </TabRegion>
-      <SaveButton
-        text={props.saveButtonText || "Deploy"}
-        onClick={onSubmit}
-        makeFlush
-        status={validationInfo.validated ? "" : validationInfo.error}
-        disabled={isReadOnly || !validationInfo.validated}
-      />
+      <br />
+      {
+        showSaveButton() && (
+          <SaveButton
+            text={props.saveButtonText || "Deploy"}
+            onClick={onSubmit}
+            makeFlush={!props.isInModal}
+            status={validationInfo.validated ? renderSaveStatus() : validationInfo.error}
+            disabled={isDisabled()}
+          /> 
+        )
+      }
       <Spacer />
     </>
   );
@@ -146,9 +196,9 @@ const Spacer = styled.div`
   height: 50px;
 `;
 
-const StyledPorterForm = styled.div`
+const StyledPorterForm = styled.div<{ showSave?: boolean }>`
   width: 100%;
-  height: 100%;
+  height: ${props => props.showSave ? 'calc(100% - 50px)' : '100%'};
   background: #ffffff11;
   color: #ffffff;
   padding: 0px 35px 25px;

+ 65 - 0
dashboard/src/components/form-refactor/PorterFormWrapper.tsx

@@ -0,0 +1,65 @@
+import React from "react";
+
+import PorterForm from "./PorterForm";
+import { PorterFormData } from "./types";
+import { PorterFormContextProvider } from "./PorterFormContextProvider";
+
+type PropsType = {
+  formData: any,
+  valuesToOverride?: any,
+  isReadOnly?: boolean,
+  onSubmit?: (values: any) => void, 
+  renderTabContents?: (currentTab: string, submitValues?: any) => any,
+  leftTabOptions?: { value: string, label: string }[],
+  rightTabOptions?: { value: string, label: string }[],
+  saveButtonText?: string,
+  isInModal?: boolean,
+  color?: string,
+  addendum?: any,
+  saveValuesStatus?: string,
+  externalValues?: any,
+};
+
+const PorterFormWrapper: React.FunctionComponent<PropsType> = ({
+  formData,
+  valuesToOverride,
+  isReadOnly,
+  onSubmit,
+  renderTabContents,
+  leftTabOptions,
+  rightTabOptions,
+  saveButtonText,
+  isInModal,
+  color,
+  addendum,
+  saveValuesStatus,
+  externalValues,
+}) => {
+  return (
+    <>
+      {formData && (formData as any).name && (
+        <PorterFormContextProvider
+          rawFormData={formData as PorterFormData}
+          overrideVariables={valuesToOverride}
+          isReadOnly={isReadOnly}
+          onSubmit={onSubmit}
+        >
+          <PorterForm
+            addendum={addendum}
+            isReadOnly={isReadOnly}
+            leftTabOptions={leftTabOptions}
+            rightTabOptions={rightTabOptions}
+            renderTabContents={renderTabContents}
+            saveButtonText={saveButtonText}
+            isInModal={isInModal}
+            color={color}
+            saveValuesStatus={saveValuesStatus}
+            externalValues={externalValues}
+          />
+        </PorterFormContextProvider>
+      )}
+    </>
+  );
+};
+
+export default PorterFormWrapper;

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

@@ -421,6 +421,7 @@ const LoadButton = styled(AddRowButton)`
 const UploadButton = styled(AddRowButton)`
   background: none;
   position: relative;
+  margin-left: 10px;
   border: 1px solid #ffffff55;
   > i {
     color: #ffffff44;

+ 15 - 40
dashboard/src/components/values-form/FormDebugger.tsx

@@ -1,7 +1,7 @@
 import React, { Component } from "react";
 import styled from "styled-components";
 import AceEditor from "react-ace";
-import PorterForm from "../form-refactor/PorterForm";
+import PorterFormWrapper from "../form-refactor/PorterFormWrapper";
 import CheckboxRow from "components/values-form/CheckboxRow";
 import InputRow from "components/values-form/InputRow";
 import yaml from "js-yaml";
@@ -11,8 +11,6 @@ import "ace-builds/src-noconflict/mode-text";
 
 import Heading from "./Heading";
 import Helper from "./Helper";
-import { PorterFormData } from "../form-refactor/types";
-import { PorterFormContextProvider } from "../form-refactor/PorterFormContextProvider";
 
 type PropsType = {
   goBack: () => void;
@@ -158,43 +156,20 @@ export default class FormDebugger extends Component<PropsType, StateType> {
 
         <Heading>🎨 Rendered Form</Heading>
         <Br />
-        {formData && (formData as any).name && (
-          <PorterFormContextProvider
-            rawFormData={formData as PorterFormData}
-            overrideVariables={{
-              input_a: this.state.valuesToOverride?.input_a?.value,
-            }}
-            isReadOnly={this.state.isReadOnly}
-            onSubmit={(vars) => {
-              alert("check console output");
-              console.log(vars);
-            }}
-          >
-            <PorterForm
-              rightTabOptions={this.state.showBonusTabs ? tabOptions : []}
-              renderTabContents={this.renderTabContents}
-              saveButtonText={"Test Submit"}
-            />
-          </PorterFormContextProvider>
-        )}
-        {/*<FormWrapper*/}
-        {/*  valuesToOverride={this.state.valuesToOverride}*/}
-        {/*  clearValuesToOverride={() =>*/}
-        {/*    this.setState({ valuesToOverride: null })*/}
-        {/*  }*/}
-        {/*  showStateDebugger={this.state.showStateDebugger}*/}
-        {/*  formData={formData}*/}
-        {/*  isReadOnly={this.state.isReadOnly}*/}
-        {/*  tabOptions={this.state.showBonusTabs ? tabOptions : []}*/}
-        {/*  renderTabContents={*/}
-        {/*    this.state.showBonusTabs ? this.renderTabContents : null*/}
-        {/*  }*/}
-        {/*  onSubmit={(values: any) => {*/}
-        {/*    alert("Check console output.");*/}
-        {/*    console.log("Raw submission values:");*/}
-        {/*    console.log(values);*/}
-        {/*  }}*/}
-        {/*/>*/}
+        <PorterFormWrapper
+          formData={formData}
+          valuesToOverride={{
+            input_a: this.state.valuesToOverride?.input_a?.value,
+          }}
+          isReadOnly={this.state.isReadOnly}
+          onSubmit={(vars) => {
+            alert("check console output");
+            console.log(vars);
+          }}
+          rightTabOptions={this.state.showBonusTabs ? tabOptions : []}
+          renderTabContents={this.renderTabContents}
+          saveButtonText={"Test Submit"}
+        />
       </StyledFormDebugger>
     );
   }

+ 31 - 20
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -24,7 +24,7 @@ import api from "shared/api";
 import ConfirmOverlay from "components/ConfirmOverlay";
 import Loading from "components/Loading";
 import StatusIndicator from "components/StatusIndicator";
-import FormWrapper from "components/values-form/FormWrapper";
+import PorterFormWrapper from "components/form-refactor/PorterFormWrapper";
 import RevisionSection from "./RevisionSection";
 import ValuesYaml from "./ValuesYaml";
 import GraphSection from "./GraphSection";
@@ -64,7 +64,8 @@ const ExpandedChart: React.FC<Props> = (props) => {
   const [devOpsMode, setDevOpsMode] = useState<boolean>(
     localStorage.getItem("devOpsMode") === "true"
   );
-  const [tabOptions, setTabOptions] = useState<any[]>([]);
+  const [rightTabOptions, setRightTabOptions] = useState<any[]>([]);
+  const [leftTabOptions, setLeftTabOptions] = useState<any[]>([]);
   const [saveValuesStatus, setSaveValueStatus] = useState<string>(null);
   const [forceRefreshRevisions, setForceRefreshRevisions] = useState<boolean>(
     false
@@ -414,17 +415,18 @@ const ExpandedChart: React.FC<Props> = (props) => {
 
   const updateTabs = () => {
     // Collate non-form tabs
-    let tabOptions = [] as any[];
-    tabOptions.push({ label: "Status", value: "status" });
+    let rightTabOptions = [] as any[];
+    let leftTabOptions = [] as any[];
+    rightTabOptions.push({ label: "Status", value: "status" });
 
     if (props.isMetricsInstalled) {
-      tabOptions.push({ label: "Metrics", value: "metrics" });
+      rightTabOptions.push({ label: "Metrics", value: "metrics" });
     }
 
-    tabOptions.push({ label: "Chart Overview", value: "graph" });
+    rightTabOptions.push({ label: "Chart Overview", value: "graph" });
 
     if (devOpsMode) {
-      tabOptions.push(
+      rightTabOptions.push(
         { label: "Manifests", value: "list" },
         { label: "Helm Values", value: "values" }
       );
@@ -432,18 +434,22 @@ const ExpandedChart: React.FC<Props> = (props) => {
 
     // Settings tab is always last
     if (isAuthorized("application", "", ["get", "delete"])) {
-      tabOptions.push({ label: "Settings", value: "settings" });
+      rightTabOptions.push({ label: "Settings", value: "settings" });
     }
 
     // Filter tabs if previewing an old revision or updating the chart version
     if (isPreview) {
       let liveTabs = ["status", "settings", "deploy", "metrics"];
-      tabOptions = tabOptions.filter(
+      rightTabOptions = rightTabOptions.filter(
+        (tab: any) => !liveTabs.includes(tab.value)
+      );
+      leftTabOptions = leftTabOptions.filter(
         (tab: any) => !liveTabs.includes(tab.value)
       );
     }
 
-    setTabOptions(tabOptions);
+    setRightTabOptions(rightTabOptions);
+    setLeftTabOptions(leftTabOptions);
   };
 
   const setRevision = (chart: ChartType, isCurrent?: boolean) => {
@@ -696,27 +702,32 @@ const ExpandedChart: React.FC<Props> = (props) => {
           />
         </HeaderWrapper>
         <BodyWrapper>
-          <FormWrapper
+          <PorterFormWrapper
+            formData={currentChart.form}
+            valuesToOverride={{
+              namespace: props.namespace,
+              clusterId: currentCluster.id,
+            }}
+            renderTabContents={renderTabContents}
             isReadOnly={
               imageIsPlaceholder ||
               !isAuthorized("application", "", ["get", "update"])
             }
-            formData={currentChart.form}
-            tabOptions={tabOptions}
-            isInModal={true}
-            renderTabContents={renderTabContents}
             onSubmit={onSubmit}
-            saveValuesStatus={saveValuesStatus}
-            externalValues={{
-              namespace: props.namespace,
-              clusterId: currentCluster.id,
-            }}
+            rightTabOptions={rightTabOptions}
+            leftTabOptions={leftTabOptions}
+            isInModal={true}
             color={isPreview ? "#f5cb42" : null}
             addendum={
               <TabButton onClick={toggleDevOpsMode} devOpsMode={devOpsMode}>
                 <i className="material-icons">offline_bolt</i> DevOps Mode
               </TabButton>
             }
+            saveValuesStatus={saveValuesStatus}
+            externalValues={{
+              namespace: props.namespace,
+              clusterId: currentCluster.id,
+            }}
           />
         </BodyWrapper>
       </StyledExpandedChart>

+ 18 - 32
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx

@@ -12,10 +12,9 @@ import api from "shared/api";
 import SaveButton from "components/SaveButton";
 import ConfirmOverlay from "components/ConfirmOverlay";
 import Loading from "components/Loading";
-import TabRegion from "components/TabRegion";
 import JobList from "./jobs/JobList";
 import SettingsSection from "./SettingsSection";
-import FormWrapper from "components/values-form/FormWrapper";
+import PorterFormWrapper from "components/form-refactor/PorterFormWrapper";
 import { PlaceHolder } from "brace";
 import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
 
@@ -33,7 +32,8 @@ type StateType = {
   newestImage: string;
   loading: boolean;
   jobs: any[];
-  tabOptions: any[];
+  leftTabOptions: any[];
+  rightTabOptions: any[];
   tabContents: any;
   currentTab: string | null;
   websockets: Record<string, any>;
@@ -51,7 +51,8 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
     newestImage: null as string,
     loading: true,
     jobs: [] as any[],
-    tabOptions: [] as any[],
+    leftTabOptions: [] as any[],
+    rightTabOptions: [] as any[],
     tabContents: [] as any,
     currentTab: null as string | null,
     websockets: {} as Record<string, any>,
@@ -485,28 +486,16 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
         formData,
       });
     }
-    let tabOptions = [] as any[];
-
-    // Append universal tabs
-    tabOptions.push({ label: "Jobs", value: "jobs" });
-
-    if (formData) {
-      formData.tabs.map((tab: any, i: number) => {
-        tabOptions.push({
-          value: tab.name,
-          label: tab.label,
-          sections: tab.sections,
-          context: tab.context,
-        });
-      });
-    }
-
+    let rightTabOptions = [] as any[];
     if (this.props.isAuthorized("job", "", ["get", "delete"])) {
-      tabOptions.push({ label: "Settings", value: "settings" });
+      rightTabOptions.push({ label: "Settings", value: "settings" });
     }
 
     // Filter tabs if previewing an old revision
-    this.setState({ tabOptions });
+    this.setState({ 
+      leftTabOptions: [{ label: "Jobs", value: "jobs" }],
+      rightTabOptions,
+    });
   }
 
   renderIcon = () => {
@@ -620,23 +609,20 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
           </HeaderWrapper>
 
           <BodyWrapper>
-            <FormWrapper
+            <PorterFormWrapper
+              formData={this.state.formData}
+              valuesToOverride={this.state.valuesToOverride}
+              isInModal={true}
+              renderTabContents={this.renderTabContents}
               isReadOnly={
                 this.state.imageIsPlaceholder ||
                 !this.props.isAuthorized("job", "", ["get", "update"])
               }
-              valuesToOverride={this.state.valuesToOverride}
-              clearValuesToOverride={() =>
-                this.setState({ valuesToOverride: {} })
-              }
-              formData={this.state.formData}
-              tabOptions={this.state.tabOptions}
-              isInModal={true}
-              renderTabContents={this.renderTabContents}
-              tabOptionsOnly={false}
               onSubmit={(formValues) =>
                 this.handleSaveValues(formValues, false)
               }
+              leftTabOptions={this.state.leftTabOptions}
+              rightTabOptions={this.state.rightTabOptions}
               saveValuesStatus={this.state.saveValuesStatus}
               saveButtonText="Save Config"
             />

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

@@ -37,7 +37,7 @@ class Dashboard extends Component<PropsType, StateType> {
     infras: [] as InfraType[],
     pressingCtrl: false,
     pressingK: false,
-    showFormDebugger: true,
+    showFormDebugger: false,
   };
 
   refreshInfras = () => {

+ 0 - 1040
dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx

@@ -1,1040 +0,0 @@
-import React, { Component } from "react";
-import styled from "styled-components";
-import randomWords from "random-words";
-import _ from "lodash";
-import { Context } from "shared/Context";
-import api from "shared/api";
-import { pushFiltered } from "shared/routing";
-import close from "assets/close.png";
-import { RouteComponentProps, withRouter } from "react-router";
-
-import {
-  ActionConfigType,
-  ChoiceType,
-  ClusterType,
-  StorageType,
-} from "shared/types";
-import Selector from "components/Selector";
-import ImageSelector from "components/image-selector/ImageSelector";
-import TabRegion from "components/TabRegion";
-import InputRow from "components/values-form/InputRow";
-import SaveButton from "components/SaveButton";
-import ActionConfEditor from "components/repo-selector/ActionConfEditor";
-import FormWrapper from "components/values-form/FormWrapper";
-import RadioSelector from "components/RadioSelector";
-import { isAlphanumeric } from "shared/common";
-
-type PropsType = RouteComponentProps & {
-  currentTemplate: any;
-  currentTab: string;
-  hideLaunch: () => void;
-  values: any;
-  form: any;
-  hideBackButton?: boolean;
-};
-
-type StateType = {
-  currentView: string;
-  clusterOptions: { label: string; value: string }[];
-  clusterMap: { [clusterId: string]: ClusterType };
-  saveValuesStatus: string | null;
-  selectedNamespace: string;
-  selectedCluster: string;
-  selectedClusterId: number;
-  selectedImageUrl: string | null;
-  sourceType: string;
-  selectedTag: string | null;
-  templateName: string;
-  tabOptions: ChoiceType[];
-  currentTab: string | null;
-  tabContents: any;
-  namespaceOptions: { label: string; value: string }[];
-  actionConfig: ActionConfigType;
-  procfileProcess: string;
-  branch: string;
-  repoType: string;
-  dockerfilePath: string | null;
-  procfilePath: string | null;
-  folderPath: string | null;
-  selectedRegistry: any | null;
-  env: any;
-  valuesToOverride: any | null;
-};
-
-const defaultActionConfig: ActionConfigType = {
-  git_repo: "",
-  image_repo_uri: "",
-  branch: "",
-  git_repo_id: 0,
-};
-
-class LaunchTemplate extends Component<PropsType, StateType> {
-  state = {
-    currentView: "repo",
-    clusterOptions: [] as { label: string; value: string }[],
-    clusterMap: {} as { [clusterId: string]: ClusterType },
-    saveValuesStatus: "" as string | null,
-    selectedCluster: this.context.currentCluster.name,
-    selectedClusterId: this.context.currentCluster.id,
-    selectedNamespace: "default",
-    selectedImageUrl: "" as string | null,
-    sourceType: "",
-    templateName: "",
-    selectedTag: "" as string | null,
-    tabOptions: [] as ChoiceType[],
-    currentTab: null as string | null,
-    tabContents: [] as any,
-    namespaceOptions: [] as { label: string; value: string }[],
-    actionConfig: { ...defaultActionConfig },
-    branch: "",
-    repoType: "",
-    dockerfilePath: null as string | null,
-    procfileProcess: null as string | null,
-    procfilePath: null as string | null,
-    folderPath: null as string | null,
-    selectedRegistry: null as any | null,
-    env: {},
-    valuesToOverride: null as any | null,
-  };
-
-  createGHAction = (chartName: string, chartNamespace: string) => {
-    let { currentProject, currentCluster } = this.context;
-    let { actionConfig } = this.state;
-    let imageRepoUri = `${this.state.selectedRegistry.url}/${chartName}-${chartNamespace}`;
-
-    // DockerHub registry integration is per repo
-    if (this.state.selectedRegistry.service === "dockerhub") {
-      imageRepoUri = this.state.selectedRegistry.url;
-    }
-
-    api
-      .createGHAction(
-        "<token>",
-        {
-          git_repo: actionConfig.git_repo,
-          git_branch: this.state.branch,
-          registry_id: this.state.selectedRegistry.id,
-          dockerfile_path: this.state.dockerfilePath,
-          folder_path: this.state.folderPath,
-          image_repo_uri: imageRepoUri,
-          git_repo_id: actionConfig.git_repo_id,
-          env: this.state.env,
-        },
-        {
-          project_id: currentProject.id,
-          CLUSTER_ID: currentCluster.id,
-          RELEASE_NAME: chartName,
-          RELEASE_NAMESPACE: chartNamespace,
-        }
-      )
-      .then((res) => console.log(""))
-      .catch(console.log);
-  };
-
-  onSubmitAddon = (wildcard?: any) => {
-    let { currentCluster, currentProject, setCurrentError } = this.context;
-    let name =
-      this.state.templateName || randomWords({ exactly: 3, join: "-" });
-    this.setState({ saveValuesStatus: "loading" });
-
-    let values = {};
-    for (let key in wildcard) {
-      _.set(values, key, wildcard[key]);
-    }
-
-    api
-      .deployTemplate(
-        "<token>",
-        {
-          templateName: this.props.currentTemplate.name,
-          storage: StorageType.Secret,
-          formValues: values,
-          namespace: this.state.selectedNamespace,
-          name,
-        },
-        {
-          id: currentProject.id,
-          cluster_id: currentCluster.id,
-          name: this.props.currentTemplate.name.toLowerCase().trim(),
-          version: this.props.currentTemplate?.currentVersion || "latest",
-          repo_url: process.env.ADDON_CHART_REPO_URL,
-        }
-      )
-      .then((_) => {
-        // this.props.setCurrentView('cluster-dashboard');
-        this.setState({ saveValuesStatus: "successful" }, () => {
-          // TODO: redirect to appropriate cluster if not current context
-          let dst =
-            this.props.currentTemplate.name === "job"
-              ? "/jobs"
-              : "/applications";
-          setTimeout(() => {
-            pushFiltered(this.props, dst, ["project_id"], {
-              cluster: currentCluster.name,
-            });
-          }, 500);
-          window.analytics.track("Deployed Add-on", {
-            name: this.props.currentTemplate.name,
-            namespace: this.state.selectedNamespace,
-            values: values,
-          });
-        });
-      })
-      .catch((err) => {
-        let parsedErr =
-          err?.response?.data?.errors && err.response.data.errors[0];
-        if (parsedErr) {
-          err = parsedErr;
-        }
-
-        this.setState({
-          saveValuesStatus: parsedErr,
-        });
-
-        setCurrentError(err);
-
-        window.analytics.track("Failed to Deploy Add-on", {
-          name: this.props.currentTemplate.name,
-          namespace: this.state.selectedNamespace,
-          values: values,
-          error: err,
-        });
-      });
-  };
-
-  onSubmit = async (rawValues: any) => {
-    let { currentCluster, currentProject } = this.context;
-    let name =
-      this.state.templateName || randomWords({ exactly: 3, join: "-" });
-    this.setState({ saveValuesStatus: "loading" });
-
-    // Convert dotted keys to nested objects
-    let values: any = {};
-    for (let key in rawValues) {
-      _.set(values, key, rawValues[key]);
-    }
-
-    let imageUrl = this.state.selectedImageUrl;
-    let tag = this.state.selectedTag;
-
-    if (this.state.selectedImageUrl.includes(":")) {
-      let splits = this.state.selectedImageUrl.split(":");
-      imageUrl = splits[0];
-      tag = splits[1];
-    } else if (!tag) {
-      tag = "latest";
-    }
-
-    if (this.state.sourceType === "repo") {
-      if (this.props.currentTemplate?.name == "job") {
-        imageUrl = "public.ecr.aws/o1j4x7p4/hello-porter-job";
-        tag = "latest";
-      } else {
-        imageUrl = "public.ecr.aws/o1j4x7p4/hello-porter";
-        tag = "latest";
-      }
-    }
-
-    let provider;
-    switch (currentCluster.service) {
-      case "eks":
-        provider = "aws";
-        break;
-      case "gke":
-        provider = "gcp";
-        break;
-      case "doks":
-        provider = "digitalocean";
-        break;
-      default:
-        provider = "";
-    }
-
-    // don't overwrite for templates that already have a source (i.e. non-Docker templates)
-    if (imageUrl && tag) {
-      _.set(values, "image.repository", imageUrl);
-      _.set(values, "image.tag", tag);
-    }
-
-    _.set(values, "ingress.provider", provider);
-    var url: string;
-    // check if template is docker and create external domain if necessary
-    if (this.props.currentTemplate.name == "web") {
-      if (values?.ingress?.enabled && !values?.ingress?.custom_domain) {
-        url = await new Promise((resolve, reject) => {
-          api
-            .createSubdomain(
-              "<token>",
-              {
-                release_name: name,
-              },
-              {
-                id: currentProject.id,
-                cluster_id: currentCluster.id,
-              }
-            )
-            .then((res) => {
-              resolve(res.data?.external_url);
-            })
-            .catch((err) => {
-              this.setState({ saveValuesStatus: "error" });
-            });
-        });
-
-        values.ingress.porter_hosts = [url];
-      }
-    }
-
-    api
-      .deployTemplate(
-        "<token>",
-        {
-          templateName: this.props.currentTemplate.name,
-          imageURL: this.state.selectedImageUrl,
-          storage: StorageType.Secret,
-          formValues: values,
-          namespace: this.state.selectedNamespace,
-          name,
-        },
-        {
-          id: currentProject.id,
-          cluster_id: currentCluster.id,
-          name: this.props.currentTemplate.name.toLowerCase().trim(),
-          version: this.props.currentTemplate?.currentVersion || "latest",
-          repo_url: process.env.APPLICATION_CHART_REPO_URL,
-        }
-      )
-      .then((_) => {
-        console.log("Deployed template.");
-        if (this.state.sourceType === "repo") {
-          console.log("Creating GHA");
-          this.createGHAction(name, this.state.selectedNamespace);
-        }
-        // this.props.setCurrentView('cluster-dashboard');
-        this.setState({ saveValuesStatus: "successful" }, () => {
-          // redirect to dashboard with namespace
-          setTimeout(() => {
-            let dst =
-              this.props.currentTemplate.name === "job"
-                ? "/jobs"
-                : "/applications";
-            pushFiltered(this.props, dst, ["project_id"], {
-              cluster: currentCluster.name,
-            });
-          }, 1000);
-        });
-      })
-      .catch((err) => {
-        this.setState({ saveValuesStatus: "error" });
-      });
-  };
-
-  submitIsDisabled = () => {
-    let {
-      templateName,
-      sourceType,
-      selectedImageUrl,
-      dockerfilePath,
-      folderPath,
-    } = this.state;
-
-    // Allow if name is invalid
-    if (templateName.length > 0 && !isAlphanumeric(templateName)) {
-      return true;
-    }
-
-    if (this.state.saveValuesStatus == "loading") {
-      return true;
-    }
-
-    if (this.props.form?.hasSource) {
-      // Allow if source type is registry and image URL is specified
-      if (sourceType === "registry" && selectedImageUrl) {
-        return false;
-      }
-
-      // Allow if source type is repo and dockerfile or folder path is set
-      if (sourceType === "repo" && (dockerfilePath || folderPath)) {
-        return !this.state.selectedRegistry;
-      }
-
-      return true;
-    } else {
-      return false;
-    }
-  };
-
-  getStatus = () => {
-    let {
-      selectedRegistry,
-      sourceType,
-      dockerfilePath,
-      folderPath,
-      procfilePath,
-    } = this.state;
-
-    if (!this.submitIsDisabled()) {
-      return this.state.saveValuesStatus;
-    }
-
-    // handle exception when deploy process is on loading
-    if (this.state.saveValuesStatus === "loading") {
-      return "loading";
-    }
-
-    if (
-      sourceType === "repo" &&
-      (dockerfilePath || folderPath) &&
-      !selectedRegistry
-    ) {
-      return "A connected container registry is required";
-    }
-    let { templateName } = this.state;
-    if (templateName.length > 0 && !isAlphanumeric(templateName)) {
-      return "Template name contains illegal characters";
-    }
-    return "No application source specified";
-  };
-
-  componentDidMount() {
-    if (this.props.currentTemplate.name !== "docker") {
-      this.setState({ saveValuesStatus: "" });
-    }
-    // Retrieve tab options
-    let tabOptions = [] as ChoiceType[];
-    this.props.form?.tabs.map((tab: any, i: number) => {
-      if (tab.context.type === "helm/values") {
-        tabOptions.push({ value: tab.name, label: tab.label });
-      }
-    });
-
-    this.setState({
-      tabOptions,
-      currentTab: tabOptions[0] && tabOptions[0]["value"],
-    });
-
-    // TODO: query with selected filter once implemented
-    let { currentProject, currentCluster } = this.context;
-    api.getClusters("<token>", {}, { id: currentProject.id }).then((res) => {
-      if (res.data) {
-        let clusterOptions: { label: string; value: string }[] = [];
-        let clusterMap: { [clusterId: string]: ClusterType } = {};
-        res.data.forEach((cluster: ClusterType, i: number) => {
-          clusterOptions.push({ label: cluster.name, value: cluster.name });
-          clusterMap[cluster.name] = cluster;
-        });
-        if (res.data.length > 0) {
-          this.setState({ clusterOptions, clusterMap });
-        }
-      }
-    });
-
-    this.updateNamespaces(currentCluster.id);
-  }
-
-  updateNamespaces = (id: number) => {
-    let { currentProject } = this.context;
-    api
-      .getNamespaces(
-        "<token>",
-        {
-          cluster_id: id,
-        },
-        { id: currentProject.id }
-      )
-      .then((res) => {
-        if (res.data) {
-          const availableNamespaces = res.data.items.filter(
-            (namespace: any) => {
-              return namespace.status.phase !== "Terminating";
-            }
-          );
-          const namespaceOptions = availableNamespaces.map(
-            (x: { metadata: { name: string } }) => {
-              return { label: x.metadata.name, value: x.metadata.name };
-            }
-          );
-          if (availableNamespaces.length > 0) {
-            this.setState({ namespaceOptions });
-          }
-        }
-      })
-      .catch(console.log);
-  };
-
-  setSelectedImageUrl = (x: string) => {
-    this.setState({ selectedImageUrl: x });
-  };
-
-  renderIcon = (icon: string) => {
-    if (icon) {
-      return <Icon src={icon} />;
-    }
-
-    return (
-      <Polymer>
-        <i className="material-icons">layers</i>
-      </Polymer>
-    );
-  };
-
-  renderSettingsRegion = () => {
-    if (this.state.tabOptions.length > 0) {
-      return (
-        <>
-          <Heading>Additional Settings</Heading>
-          <Subtitle>
-            Configure additional settings for this template. (Optional)
-          </Subtitle>
-          <FormWrapper
-            formData={this.props.form}
-            saveValuesStatus={this.state.saveValuesStatus}
-            valuesToOverride={this.state.valuesToOverride}
-            clearValuesToOverride={() =>
-              this.setState({ valuesToOverride: null })
-            }
-            externalValues={{
-              namespace: this.state.selectedNamespace,
-              clusterId: this.context.currentCluster.id,
-              isLaunch: true,
-            }}
-            onSubmit={
-              this.props.currentTab === "docker"
-                ? this.onSubmit
-                : this.onSubmitAddon
-            }
-          />
-        </>
-      );
-    } else {
-      return (
-        <Wrapper>
-          <Placeholder>
-            To configure this chart through Porter,
-            <Link
-              target="_blank"
-              href="https://docs.getporter.dev/docs/add-ons"
-            >
-              refer to our docs
-            </Link>
-            .
-          </Placeholder>
-          <SaveButton
-            text="Deploy"
-            onClick={this.onSubmitAddon}
-            status={this.state.saveValuesStatus}
-            makeFlush={true}
-          />
-        </Wrapper>
-      );
-    }
-  };
-
-  // Display if current template uses source (image or repo)
-  renderSourceSelectorContent = () => {
-    let { capabilities } = this.context;
-
-    if (this.state.sourceType === "") {
-      return (
-        <BlockList>
-          {capabilities.github && (
-            <Block
-              onClick={() => {
-                this.setState({ sourceType: "repo" });
-              }}
-            >
-              <BlockIcon src="https://3.bp.blogspot.com/-xhNpNJJyQhk/XIe4GY78RQI/AAAAAAAAItc/ouueFUj2Hqo5dntmnKqEaBJR4KQ4Q2K3ACK4BGAYYCw/s1600/logo%2Bgit%2Bicon.png" />
-              <BlockTitle>Git Repository</BlockTitle>
-              <BlockDescription>
-                Deploy using source from a Git repo.
-              </BlockDescription>
-            </Block>
-          )}
-          <Block
-            onClick={() => {
-              this.setState({ sourceType: "registry" });
-            }}
-          >
-            <BlockIcon src="https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png" />
-            <BlockTitle>Docker Registry</BlockTitle>
-            <BlockDescription>
-              Deploy a container from an image registry.
-            </BlockDescription>
-          </Block>
-        </BlockList>
-      );
-    } else if (this.state.sourceType === "registry") {
-      return (
-        <StyledSourceBox>
-          <CloseButton
-            onClick={() =>
-              this.setState({
-                sourceType: "",
-                selectedImageUrl: "",
-                selectedTag: "",
-              })
-            }
-          >
-            <CloseButtonImg src={close} />
-          </CloseButton>
-          <Subtitle>
-            Specify the container image you would like to connect to this
-            template.
-            <Highlight
-              onClick={() =>
-                pushFiltered(this.props, "/integrations/registry", [
-                  "project_id",
-                ])
-              }
-            >
-              Manage Docker registries
-            </Highlight>
-            <Required>*</Required>
-          </Subtitle>
-          <DarkMatter antiHeight="-4px" />
-          <ImageSelector
-            selectedTag={this.state.selectedTag}
-            selectedImageUrl={this.state.selectedImageUrl}
-            setSelectedImageUrl={this.setSelectedImageUrl}
-            setSelectedTag={(x: string) => this.setState({ selectedTag: x })}
-            forceExpanded={true}
-          />
-          <br />
-        </StyledSourceBox>
-      );
-    } else {
-      return (
-        <StyledSourceBox>
-          <CloseButton onClick={() => this.setState({ sourceType: "" })}>
-            <CloseButtonImg src={close} />
-          </CloseButton>
-          <Subtitle>
-            Provide a repo folder to use as source.
-            <Highlight
-              onClick={() =>
-                pushFiltered(this.props, "/integrations/repo", ["project_id"])
-              }
-            >
-              Manage Git repos
-            </Highlight>
-            <Required>*</Required>
-          </Subtitle>
-          <DarkMatter antiHeight="-4px" />
-          <ActionConfEditor
-            actionConfig={this.state.actionConfig}
-            branch={this.state.branch}
-            setActionConfig={(actionConfig: ActionConfigType) =>
-              this.setState({ actionConfig }, () => {
-                this.setSelectedImageUrl(
-                  this.state.actionConfig.image_repo_uri
-                );
-              })
-            }
-            procfileProcess={this.state.procfileProcess}
-            setProcfileProcess={(procfileProcess: string) =>
-              this.setState({
-                procfileProcess,
-                valuesToOverride: {
-                  "container.command": {
-                    value: procfileProcess || "",
-                  },
-                  showStartCommand: {
-                    value: !procfileProcess,
-                  },
-                },
-              })
-            }
-            setBranch={(branch: string) => this.setState({ branch })}
-            setDockerfilePath={(x: string) =>
-              this.setState({ dockerfilePath: x })
-            }
-            setProcfilePath={(x: string) => {
-              this.setState({ procfilePath: x });
-            }}
-            procfilePath={this.state.procfilePath}
-            dockerfilePath={this.state.dockerfilePath}
-            folderPath={this.state.folderPath}
-            setFolderPath={(x: string) => this.setState({ folderPath: x })}
-            reset={() => {
-              this.setState({
-                actionConfig: { ...defaultActionConfig },
-                branch: "",
-                dockerfilePath: null,
-                folderPath: null,
-              });
-            }}
-            setSelectedRegistry={(x: any) => {
-              this.setState({ selectedRegistry: x });
-            }}
-            selectedRegistry={this.state.selectedRegistry}
-          />
-          <br />
-        </StyledSourceBox>
-      );
-    }
-  };
-
-  renderSourceSelector = () => {
-    return (
-      <>
-        <Heading>Deployment Method</Heading>
-        <Subtitle>
-          Choose the deployment method you would like to use for this
-          application.
-          <Required>*</Required>
-        </Subtitle>
-        {this.renderSourceSelectorContent()}
-      </>
-    );
-  };
-
-  render() {
-    console.log("RENDERING");
-    let { name, icon } = this.props.currentTemplate;
-    let { currentTemplate } = this.props;
-
-    return (
-      <StyledLaunchTemplate>
-        <HeaderSection>
-          <i className="material-icons" onClick={this.props.hideLaunch}>
-            keyboard_backspace
-          </i>
-          {icon ? this.renderIcon(icon) : this.renderIcon(currentTemplate.icon)}
-          <Title>{name}</Title>
-        </HeaderSection>
-        <DarkMatter antiHeight="-13px" />
-        <Heading isAtTop={true}>Name</Heading>
-        <Subtitle>
-          Randomly generated if left blank.
-          <Warning
-            highlight={
-              !isAlphanumeric(this.state.templateName) &&
-              this.state.templateName !== ""
-            }
-          >
-            Lowercase letters, numbers, and "-" only.
-          </Warning>
-        </Subtitle>
-        <DarkMatter antiHeight="-29px" />
-        <InputRow
-          type="text"
-          value={this.state.templateName}
-          setValue={(x: string) => this.setState({ templateName: x })}
-          placeholder="ex: doctor-scientist"
-          width="100%"
-        />
-
-        {this.props.form?.hasSource && this.renderSourceSelector()}
-
-        <Heading>Destination</Heading>
-        <Subtitle>
-          Specify the cluster and namespace you would like to deploy your
-          application to.
-        </Subtitle>
-        <ClusterSection>
-          <ClusterLabel>
-            <i className="material-icons">device_hub</i>Cluster
-          </ClusterLabel>
-          <Selector
-            activeValue={this.state.selectedCluster}
-            setActiveValue={(cluster: string) => {
-              this.context.setCurrentCluster(this.state.clusterMap[cluster]);
-              this.updateNamespaces(this.state.clusterMap[cluster].id);
-              this.setState({
-                selectedCluster: cluster,
-                selectedClusterId: this.state.clusterMap[cluster].id,
-              });
-            }}
-            options={this.state.clusterOptions}
-            width="250px"
-            dropdownWidth="335px"
-            closeOverlay={true}
-          />
-          <NamespaceLabel>
-            <i className="material-icons">view_list</i>Namespace
-          </NamespaceLabel>
-          <Selector
-            key={"namespace"}
-            activeValue={this.state.selectedNamespace}
-            setActiveValue={(namespace: string) =>
-              this.setState({ selectedNamespace: namespace })
-            }
-            addButton={true}
-            options={this.state.namespaceOptions}
-            width="250px"
-            dropdownWidth="335px"
-            closeOverlay={true}
-          />
-        </ClusterSection>
-        {this.renderSettingsRegion()}
-      </StyledLaunchTemplate>
-    );
-  }
-}
-
-LaunchTemplate.contextType = Context;
-export default withRouter(LaunchTemplate);
-
-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 BlockIcon = styled.img<{ bw?: boolean }>`
-  height: 38px;
-  padding: 2px;
-  margin-top: 30px;
-  margin-bottom: 15px;
-  filter: ${(props) => (props.bw ? "grayscale(1)" : "")};
-`;
-
-const BlockDescription = styled.div`
-  margin-bottom: 12px;
-  color: #ffffff66;
-  text-align: center;
-  font-weight: default;
-  font-size: 13px;
-  padding: 0px 25px;
-  height: 2.4em;
-  font-size: 12px;
-  display: -webkit-box;
-  overflow: hidden;
-  -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
-`;
-
-const BlockTitle = styled.div`
-  margin-bottom: 12px;
-  width: 80%;
-  text-align: center;
-  font-size: 14px;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const Block = styled.div<{ disabled?: boolean }>`
-  align-items: center;
-  user-select: none;
-  border-radius: 5px;
-  display: flex;
-  font-size: 13px;
-  overflow: hidden;
-  font-weight: 500;
-  padding: 3px 0px 12px;
-  flex-direction: column;
-  align-item: center;
-  justify-content: space-between;
-  height: 170px;
-  cursor: ${(props) => (props.disabled ? "" : "pointer")};
-  color: #ffffff;
-  position: relative;
-  background: #26282f;
-  box-shadow: 0 3px 5px 0px #00000022;
-  :hover {
-    background: ${(props) => (props.disabled ? "" : "#ffffff11")};
-  }
-
-  animation: fadeIn 0.3s 0s;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
-const BlockList = styled.div`
-  overflow: visible;
-  margin-top: 6px;
-  margin-bottom: 27px;
-  display: grid;
-  grid-column-gap: 25px;
-  grid-row-gap: 25px;
-  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-`;
-
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 10px;
-  border-radius: 2px;
-  color: #ffffff;
-`;
-
-const HeaderSection = styled.div`
-  display: flex;
-  align-items: center;
-  margin-bottom: 30px;
-
-  > i {
-    cursor: pointer;
-    font-size 24px;
-    color: #969Fbbaa;
-    padding: 3px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-  }
-`;
-
-const Heading = styled.div<{ isAtTop?: boolean }>`
-  color: white;
-  font-weight: 500;
-  font-size: 16px;
-  margin-bottom: 5px;
-  margin-top: ${(props) => (props.isAtTop ? "10px" : "30px")};
-  display: flex;
-  align-items: center;
-`;
-
-const Warning = styled.span<{ highlight: boolean; makeFlush?: boolean }>`
-  color: ${(props) => (props.highlight ? "#f5cb42" : "")};
-  margin-left: ${(props) => (props.makeFlush ? "" : "5px")};
-`;
-
-const Required = styled.div`
-  margin-left: 8px;
-  color: #fc4976;
-  display: inline-block;
-`;
-
-const Link = styled.a`
-  margin-left: 5px;
-`;
-
-const Wrapper = styled.div`
-  width: 100%;
-  position: relative;
-  padding-top: 20px;
-  padding-bottom: 70px;
-`;
-
-const Placeholder = styled.div`
-  width: 100%;
-  height: 200px;
-  background: #ffffff11;
-  border: 1px solid #ffffff44;
-  border-radius: 5px;
-  color: #aaaabb;
-  font-size: 13px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-`;
-
-const DarkMatter = styled.div<{ antiHeight?: string }>`
-  width: 100%;
-  margin-top: ${(props) => props.antiHeight || "-15px"};
-`;
-
-const Subtitle = styled.div`
-  padding: 11px 0px 16px;
-  font-family: "Work Sans", sans-serif;
-  font-size: 13px;
-  color: #aaaabb;
-  line-height: 1.6em;
-  display: flex;
-  align-items: center;
-`;
-
-const ClusterLabel = styled.div`
-  margin-right: 10px;
-  display: flex;
-  align-items: center;
-  > i {
-    font-size: 16px;
-    margin-right: 6px;
-  }
-`;
-
-const NamespaceLabel = styled.div`
-  margin-left: 15px;
-  margin-right: 10px;
-  display: flex;
-  align-items: center;
-  > i {
-    font-size: 16px;
-    margin-right: 6px;
-  }
-`;
-
-const Icon = styled.img`
-  width: 21px;
-  margin-right: 6px;
-  margin-left: 10px;
-`;
-
-const Polymer = styled.div`
-  margin-bottom: -3px;
-
-  > i {
-    color: ${(props) => props.theme.containerIcon};
-    font-size: 18px;
-    margin-right: 10px;
-  }
-`;
-
-const ClusterSection = styled.div`
-  display: flex;
-  align-items: center;
-  color: #ffffff;
-  font-family: "Work Sans", sans-serif;
-  font-size: 14px;
-  margin-top: 2px;
-  font-weight: 500;
-  margin-bottom: 32px;
-
-  > i {
-    font-size: 25px;
-    color: #ffffff44;
-    margin-right: 13px;
-  }
-`;
-
-const StyledLaunchTemplate = styled.div`
-  width: 100%;
-  padding-bottom: 150px;
-`;
-
-const Highlight = styled.div`
-  color: #8590ff;
-  text-decoration: none;
-  margin-left: 5px;
-  cursor: pointer;
-`;
-
-const StyledSourceBox = styled.div`
-  width: 100%;
-  height: 100%;
-  background: #ffffff11;
-  color: #ffffff;
-  padding: 14px 35px 20px;
-  position: relative;
-  border-radius: 5px;
-  font-size: 13px;
-  margin-top: 6px;
-  overflow: auto;
-  margin-bottom: 25px;
-`;

+ 5 - 8
dashboard/src/main/home/launch/launch-flow/SettingsPage.tsx

@@ -16,7 +16,7 @@ import { isAlphanumeric } from "shared/common";
 import InputRow from "components/values-form/InputRow";
 import SaveButton from "components/SaveButton";
 import Helper from "components/values-form/Helper";
-import FormWrapper from "components/values-form/FormWrapper";
+import PorterFormWrapper from "components/form-refactor/PorterFormWrapper";
 import Selector from "components/Selector";
 import Loading from "components/Loading";
 import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
@@ -143,20 +143,17 @@ class SettingsPage extends Component<PropsType, StateType> {
           <Helper>
             Configure additional settings for this template. (Optional)
           </Helper>
-          <FormWrapper
+          <PorterFormWrapper
             formData={form}
-            saveValuesStatus={saveValuesStatus}
             valuesToOverride={valuesToOverride}
-            clearValuesToOverride={clearValuesToOverride}
+            isReadOnly={!this.props.isAuthorized("namespace", "", ["get", "create"])}
+            onSubmit={onSubmit}
+            saveValuesStatus={saveValuesStatus}
             externalValues={{
               namespace: selectedNamespace,
               clusterId: this.context.currentCluster.id,
               isLaunch: true,
             }}
-            isReadOnly={
-              !this.props.isAuthorized("namespace", "", ["get", "create"])
-            }
-            onSubmit={onSubmit}
           />
         </>
       );