Przeglądaj źródła

Use Contract for Preflight Checks (#3928)

Co-authored-by: ianedwards <ianedwards559@gmail.com>
sdess09 2 lat temu
rodzic
commit
22b005940f
1 zmienionych plików z 288 dodań i 290 usunięć
  1. 288 290
      dashboard/src/components/ProvisionerSettings.tsx

+ 288 - 290
dashboard/src/components/ProvisionerSettings.tsx

@@ -30,12 +30,9 @@ import {
 
 import { ClusterType } from "shared/types";
 import Button from "./porter/Button";
-import Error from "./porter/Error";
 import healthy from "assets/status-healthy.png";
 
 import Spacer from "./porter/Spacer";
-import Step from "./porter/Step";
-import Link from "./porter/Link";
 import Text from "./porter/Text";
 import Select from "./porter/Select";
 import Input from "./porter/Input";
@@ -44,11 +41,33 @@ import Tooltip from "./porter/Tooltip";
 import Icon from "./porter/Icon";
 import Loading from "./Loading";
 import PreflightChecks from "./PreflightChecks";
-import Placeholder from "./Placeholder";
 import VerticalSteps from "./porter/VerticalSteps";
-import Modal from "components/porter/Modal";
 import { PREFLIGHT_TO_ENUM } from "shared/util";
 import { useIntercom } from "lib/hooks/useIntercom";
+
+
+type ClusterState = {
+  clusterName: string;
+  awsRegion: string;
+  machineType: string;
+  guardDutyEnabled: boolean;
+  kmsEncryptionEnabled: boolean;
+  loadBalancerType: boolean;
+  wildCardDomain: string;
+  IPAllowList: string;
+  wafV2Enabled: boolean;
+  awsTags: string;
+  wafV2ARN: string;
+  certificateARN: string;
+  minInstances: number;
+  maxInstances: number;
+  additionalNodePolicies: string[];
+  cidrRangeVPC: string;
+  cidrRangeServices: string;
+  clusterVersion: string;
+};
+
+
 const regionOptions = [
   { value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
   { value: "us-east-2", label: "US East (Ohio) us-east-2" },
@@ -110,6 +129,29 @@ const defaultCidrVpc = "10.78.0.0/16"
 const defaultCidrServices = "172.20.0.0/16"
 const defaultClusterVersion = "v1.24.0"
 
+
+const initialClusterState: ClusterState = {
+  clusterName: "",
+  awsRegion: "us-east-1",
+  machineType: "t3.medium",
+  guardDutyEnabled: false,
+  kmsEncryptionEnabled: false,
+  loadBalancerType: false,
+  wildCardDomain: "",
+  IPAllowList: "",
+  wafV2Enabled: false,
+  awsTags: "",
+  wafV2ARN: "",
+  certificateARN: "",
+  minInstances: 1,
+  maxInstances: 10,
+  additionalNodePolicies: [],
+  cidrRangeVPC: defaultCidrVpc,
+  cidrRangeServices: defaultCidrServices,
+  clusterVersion: defaultClusterVersion,
+};
+
+
 type Props = RouteComponentProps & {
   selectedClusterVersion?: Contract;
   provisionerError?: string;
@@ -126,87 +168,63 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
     setCurrentCluster,
     setShouldRefreshClusters,
   } = useContext(Context);
-  const [clusterName, setClusterName] = useState("");
-  const [awsRegion, setAwsRegion] = useState("us-east-1");
-  const [machineType, setMachineType] = useState("t3.medium");
-  const [guardDutyEnabled, setGuardDutyEnabled] = useState<boolean>(false);
-  const [kmsEncryptionEnabled, setKmsEncryptionEnabled] = useState<boolean>(
-    false
-  );
   const [step, setStep] = useState(0);
-  const [loadBalancerType, setLoadBalancerType] = useState(false);
-  const [wildCardDomain, setWildCardDomain] = useState("");
-  const [IPAllowList, setIPAllowList] = useState<string>("");
-  const [controlPlaneLogs, setControlPlaneLogs] = useState<EKSLogging>(
-    new EKSLogging()
-  );
-  //const [accessS3Logs, setAccessS3Logs] = useState<boolean>(false)
-  const [wafV2Enabled, setWaf2Enabled] = useState<boolean>(false);
-  const [awsTags, setAwsTags] = useState<string>("");
-  const [wafV2ARN, setwafV2ARN] = useState("");
-  const [certificateARN, seCertificateARN] = useState("");
-  const [isExpanded, setIsExpanded] = useState(false);
-  const [minInstances, setMinInstances] = useState(1);
-  const [maxInstances, setMaxInstances] = useState(10);
-  const [additionalNodePolicies, setAdditionalNodePolicies] = useState<
-    string[]
-  >([]);
-  const [cidrRangeVPC, setCidrRangeVPC] = useState(defaultCidrVpc);
-  const [cidrRangeServices, setCidrRangeServices] = useState(defaultCidrServices);
-  const [clusterVersion, setClusterVersion] = useState(defaultClusterVersion);
+
   const [isReadOnly, setIsReadOnly] = useState(false);
-  const [errorMessage, setErrorMessage] = useState<string>(undefined);
   const [isClicked, setIsClicked] = useState(false);
   const [isLoading, setIsLoading] = useState(false);
   const [preflightData, setPreflightData] = useState(null)
   const [preflightFailed, setPreflightFailed] = useState<boolean>(true)
   const [preflightError, setPreflightError] = useState<string>("")
-  const [showPreflightModal, setShowPreflightModal] = useState(false);
   const [showHelpMessage, setShowHelpMessage] = useState(true);
   const [quotaIncrease, setQuotaIncrease] = useState<EnumQuotaIncrease[]>([]);
   const [showEmailMessage, setShowEmailMessage] = useState(false);
   const { showIntercomWithMessage } = useIntercom();
+  const [clusterState, setClusterState] = useState(initialClusterState);
+  const [isExpanded, setIsExpanded] = useState(false);
+  const [controlPlaneLogs, setControlPlaneLogs] = useState<EKSLogging>(
+    new EKSLogging()
+  );
 
 
-  const markStepStarted = async (step: string, errMessage?: string) => {
+  const markStepStarted = async (step: string, errMessage?: string): Promise<void> => {
     try {
       await api.updateOnboardingStep(
         "<token>",
         {
           step,
           error_message: errMessage,
-          region: awsRegion,
+          region: clusterState.awsRegion,
           provider: "aws",
         },
         {
-          project_id: currentProject.id,
+          project_id: currentProject ? currentProject.id : 0,
         }
       );
     } catch (err) {
-      // console.log(err);
     }
   };
 
-  const getStatus = () => {
+  const getStatus = (): JSX.Element | "Provisioning is still in progress..." | undefined => {
     if (isLoading) {
       return <Loading />
     }
-    if (isReadOnly && props.provisionerError == "") {
+    if (isReadOnly && props.provisionerError === "") {
       return "Provisioning is still in progress...";
 
     }
     return undefined;
   };
-  const validateInput = (wildCardDomainer) => {
+  const validateInput = (wildCardDomainer: string): false | string => {
     if (!wildCardDomainer) {
       return "Required for ALB Load Balancer";
     }
-    if (wildCardDomainer?.charAt(0) == "*") {
+    if (wildCardDomainer?.charAt(0) === "*") {
       return "Wildcard domain cannot start with *";
     }
     return false;
   };
-  function validateIPInput(IPAllowList) {
+  function validateIPInput(IPAllowList: string): boolean {
     // This regular expression checks for an IP address with a subnet mask.
     const regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[1-2][0-9]|3[0-2])$/;
     if (!IPAllowList) {
@@ -224,7 +242,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
     // If all IPs are valid, return false (no error)
     return false;
   }
-  function validateTags(awsTags) {
+  function validateTags(awsTags: string): boolean {
     // Regular expression t o check for a key-value pair format "key=value"
     const regex = /^[a-zA-Z0-9]+=[a-zA-Z0-9]+$/;
     // Split the input string by comma and remove any empty elements
@@ -239,62 +257,68 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
     // If all tags are valid, return false (no error)
     return false;
   }
-  const clusterNameDoesNotExist = () => {
-    return !clusterName;
+  const clusterNameDoesNotExist = (): boolean => {
+    return !clusterState.clusterName;
   };
-  const userProvisioning = () => {
+  const userProvisioning = (): boolean => {
     //If the cluster is updating or updating unavailabe but there are no errors do not allow re-provisioning
     return isReadOnly && props.provisionerError === "";
   };
 
-  const isDisabled = () => {
+  const isDisabled = (): boolean | undefined => {
     return (
       (clusterNameDoesNotExist() || userProvisioning() || isClicked || (currentCluster && !currentProject?.enable_reprovision)
       ))
   };
-  function convertStringToTags(tagString) {
+  function convertStringToTags(tagString: string): { [key: string]: string } {
     if (typeof tagString !== "string" || tagString.trim() === "") {
-      return [];
+      return {};
     }
 
     // Split the input string by comma, then reduce the resulting array to an object
-    const tags = tagString.split(",").reduce((obj, item) => {
-      // Split each item by "=",
-      const [key, value] = item.split("=");
-      // Add the key-value pair to the object
-      obj[key] = value;
+    const tags = tagString.split(",").reduce<{ [key: string]: string }>((obj, item) => {
+      // Split each item by "=", and trim whitespace from both key and value
+      const [key, value] = item.split("=").map(part => part.trim());
+
+      // Only add the key-value pair to the object if both key and value are present
+      if (key && value) {
+        obj[key] = value;
+      }
+
       return obj;
     }, {});
 
     return tags;
   }
-  const createCluster = async () => {
-    setIsLoading(true);
-    setIsClicked(true);
-
-
+  const handleClusterStateChange = <K extends keyof ClusterState>(
+    key: K,
+    value: ClusterState[K]
+  ): void => {
+    setClusterState((prevState: ClusterState) => ({ ...prevState, [key]: value }));
+  };
+  const createClusterObj = (): Contract => {
     let loadBalancerObj = new LoadBalancer({});
     loadBalancerObj.loadBalancerType = LoadBalancerType.NLB;
-    if (loadBalancerType) {
+    if (clusterState.loadBalancerType) {
       loadBalancerObj.loadBalancerType = LoadBalancerType.ALB;
-      loadBalancerObj.wildcardDomain = wildCardDomain;
+      loadBalancerObj.wildcardDomain = clusterState.wildCardDomain;
 
-      if (awsTags) {
-        loadBalancerObj.tags = convertStringToTags(awsTags);
+      if (clusterState.awsTags) {
+        loadBalancerObj.tags = convertStringToTags(clusterState.awsTags);
       }
-      if (IPAllowList) {
-        loadBalancerObj.allowlistIpRanges = IPAllowList;
+      if (clusterState.IPAllowList) {
+        loadBalancerObj.allowlistIpRanges = clusterState.IPAllowList;
       }
-      if (wafV2Enabled) {
-        loadBalancerObj.enableWafv2 = wafV2Enabled;
+      if (clusterState.wafV2Enabled) {
+        loadBalancerObj.enableWafv2 = clusterState.wafV2Enabled;
       } else {
         loadBalancerObj.enableWafv2 = false;
       }
-      if (wafV2ARN) {
-        loadBalancerObj.wafv2Arn = wafV2ARN;
+      if (clusterState.wafV2ARN) {
+        loadBalancerObj.wafv2Arn = clusterState.wafV2ARN;
       }
-      if (certificateARN) {
-        loadBalancerObj.additionalCertificateArns = certificateARN.split(",");
+      if (clusterState.certificateARN) {
+        loadBalancerObj.additionalCertificateArns = clusterState.certificateARN.split(",");
       }
     }
 
@@ -307,17 +331,17 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
         kindValues: {
           case: "eksKind",
           value: new EKS({
-            clusterName,
-            clusterVersion: clusterVersion || defaultClusterVersion,
-            cidrRange: cidrRangeVPC || defaultCidrVpc, // deprecated in favour of network.cidrRangeVPC: can be removed after december 2023
-            region: awsRegion,
+            clusterName: clusterState.clusterName,
+            clusterVersion: clusterState.clusterVersion || defaultClusterVersion,
+            cidrRange: clusterState.cidrRangeVPC || defaultCidrVpc, // deprecated in favour of network.cidrRangeVPC: can be removed after december 2023
+            region: clusterState.awsRegion,
             loadBalancer: loadBalancerObj,
             logging: controlPlaneLogs,
-            enableGuardDuty: guardDutyEnabled,
-            enableKmsEncryption: kmsEncryptionEnabled,
+            enableGuardDuty: clusterState.guardDutyEnabled,
+            enableKmsEncryption: clusterState.kmsEncryptionEnabled,
             network: new AWSClusterNetwork({
-              vpcCidr: cidrRangeVPC || defaultCidrVpc,
-              serviceCidr: cidrRangeServices || defaultCidrServices,
+              vpcCidr: clusterState.cidrRangeVPC || defaultCidrVpc,
+              serviceCidr: clusterState.cidrRangeServices || defaultCidrServices,
             }),
             nodeGroups: [
               new EKSNodeGroup({
@@ -326,7 +350,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                 maxInstances: 5,
                 nodeGroupType: NodeGroupType.SYSTEM,
                 isStateful: false,
-                additionalPolicies: additionalNodePolicies,
+                additionalPolicies: clusterState.additionalNodePolicies,
               }),
               new EKSNodeGroup({
                 instanceType: "t3.large",
@@ -334,32 +358,36 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                 maxInstances: 1,
                 nodeGroupType: NodeGroupType.MONITORING,
                 isStateful: true,
-                additionalPolicies: additionalNodePolicies,
+                additionalPolicies: clusterState.additionalNodePolicies,
               }),
               new EKSNodeGroup({
-                instanceType: machineType,
-                minInstances: minInstances || 1,
-                maxInstances: maxInstances || 10,
+                instanceType: clusterState.machineType,
+                minInstances: clusterState.minInstances || 1,
+                maxInstances: clusterState.maxInstances || 10,
                 nodeGroupType: NodeGroupType.APPLICATION,
                 isStateful: false,
-                additionalPolicies: additionalNodePolicies,
+                additionalPolicies: clusterState.additionalNodePolicies,
               }),
             ],
           }),
         },
       }),
     });
+    return data;
+  }
 
+  const createCluster = async (): Promise<void> => {
+    setIsLoading(true);
+    setIsClicked(true);
+    const data = createClusterObj();
     if (props.clusterId) {
       data["cluster"]["clusterId"] = props.clusterId;
     }
 
     try {
       setIsReadOnly(true);
-      setErrorMessage(undefined);
-
       if (!props.clusterId) {
-        markStepStarted("pre-provisioning-check-started");
+        await markStepStarted("pre-provisioning-check-started");
       }
 
       const res = await api.createContract("<token>", data, {
@@ -367,7 +395,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
       });
 
       if (!props.clusterId) {
-        markStepStarted("provisioning-started");
+        await markStepStarted("provisioning-started");
       }
 
       // Only refresh and set clusters on initial create
@@ -388,33 +416,17 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
           });
         })
         .catch((err) => {
-          console.error(err);
         });
       // }
-      {
-        props?.closeModal &&
-          props?.closeModal()
-      };
+      if (props?.closeModal) {
+        props?.closeModal()
+      }
 
-      setErrorMessage(undefined);
     } catch (err) {
       const errMessage = err.response.data?.error.replace("unknown: ", "");
       // hacky, need to standardize error contract with backend
       setIsClicked(false);
       setIsLoading(false)
-      if (errMessage.includes("elastic IP")) {
-        setErrorMessage(AWS_EIP_QUOTA_ERROR_MESSAGE);
-      } else if (errMessage.includes("VPC")) {
-        setErrorMessage(AWS_VPC_QUOTA_ERROR_MESSAGE);
-      } else if (errMessage.includes("NAT Gateway")) {
-        setErrorMessage(AWS_NAT_GATEWAY_QUOTA_ERROR_MESSAGE);
-      } else if (errMessage.includes("vCPU")) {
-        setErrorMessage(AWS_VCPU_QUOTA_ERROR_MESSAGE);
-      } else if (errMessage.includes("AWS account")) {
-        setErrorMessage(AWS_LOGIN_ERROR_MESSAGE);
-      } else {
-        setErrorMessage(DEFAULT_ERROR_MESSAGE);
-      }
       markStepStarted("provisioning-failed", errMessage);
 
       // enable edit again only in the case of an error
@@ -431,7 +443,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
       (currentCluster.status === "UPDATING" ||
         currentCluster.status === "UPDATING_UNAVAILABLE")
     );
-    setClusterName(
+    handleClusterStateChange("clusterName",
       `${currentProject.name}-cluster-${Math.random()
         .toString(36)
         .substring(2, 8)}`
@@ -442,64 +454,58 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
     const contract = props.selectedClusterVersion as any;
     if (contract?.cluster) {
       let eksValues: EKS = contract.cluster?.eksKind as EKS;
-      if (eksValues == null) {
+      if (eksValues === null) {
         return;
       }
-      eksValues.nodeGroups.map((nodeGroup: EKSNodeGroup) => {
-        if (
-          nodeGroup.nodeGroupType.toString() === "NODE_GROUP_TYPE_APPLICATION"
-        ) {
-          setMachineType(nodeGroup.instanceType);
-          setMinInstances(nodeGroup.minInstances);
-          setMaxInstances(nodeGroup.maxInstances);
+
+      eksValues.nodeGroups.forEach((nodeGroup: EKSNodeGroup) => {
+        if (nodeGroup.nodeGroupType.toString() === "NODE_GROUP_TYPE_APPLICATION") {
+          handleClusterStateChange('machineType', nodeGroup.instanceType);
+          handleClusterStateChange('minInstances', nodeGroup.minInstances);
+          handleClusterStateChange('maxInstances', nodeGroup.maxInstances);
         }
 
         if (nodeGroup.additionalPolicies?.length > 0) {
-          // this shares policies across all node groups, but there is no reason that this can be specific policies per node group
-          setAdditionalNodePolicies(nodeGroup.additionalPolicies);
+          handleClusterStateChange('additionalNodePolicies', nodeGroup.additionalPolicies);
         }
       });
-      setClusterName(eksValues.clusterName);
-      setAwsRegion(eksValues.region);
-      setClusterVersion(eksValues.clusterVersion);
-      setCidrRangeVPC(eksValues.cidrRange);
-      if (eksValues.network != null) {
-        setCidrRangeVPC(eksValues.network?.vpcCidr || defaultCidrVpc);
-        setCidrRangeServices(eksValues.network?.serviceCidr || defaultCidrServices);
-      }
-      if (eksValues.loadBalancer != null) {
-        setIPAllowList(eksValues.loadBalancer.allowlistIpRanges);
-        setWildCardDomain(eksValues.loadBalancer.wildcardDomain);
-        //setAccessS3Logs(eksValues.loadBalancer.enableS3AccessLogs)
-
-        if (eksValues.loadBalancer.tags) {
-          setAwsTags(
-            Object.entries(eksValues.loadBalancer.tags)
-              .map(([key, value]) => `${key}=${value}`)
-              .join(",")
-          );
-        }
 
-        setLoadBalancerType(
-          eksValues.loadBalancer.loadBalancerType?.toString() ===
-          "LOAD_BALANCER_TYPE_ALB"
-        );
-        setwafV2ARN(eksValues.loadBalancer.wafv2Arn);
-        setWaf2Enabled(eksValues.loadBalancer.enableWafv2);
+      handleClusterStateChange('clusterName', eksValues.clusterName);
+      handleClusterStateChange('awsRegion', eksValues.region);
+      handleClusterStateChange('clusterVersion', eksValues.clusterVersion);
+      handleClusterStateChange('cidrRangeVPC', eksValues.cidrRange ?? eksValues.network?.vpcCidr ?? defaultCidrVpc);
+      handleClusterStateChange('cidrRangeServices', eksValues.network?.serviceCidr || defaultCidrServices);
+
+      if (eksValues.loadBalancer !== null) {
+        handleClusterStateChange('IPAllowList', eksValues.loadBalancer.allowlistIpRanges);
+        handleClusterStateChange('wildCardDomain', eksValues.loadBalancer.wildcardDomain);
+
+        const awsTags = eksValues.loadBalancer.tags
+          ? Object.entries(eksValues.loadBalancer.tags)
+            .map(([key, value]) => `${key}=${value}`)
+            .join(",")
+          : '';
+        handleClusterStateChange('awsTags', awsTags);
+
+        const loadBalancerType = eksValues.loadBalancer.loadBalancerType?.toString() === "LOAD_BALANCER_TYPE_ALB";
+        handleClusterStateChange('loadBalancerType', loadBalancerType);
+        handleClusterStateChange('wafV2ARN', eksValues.loadBalancer.wafv2Arn);
+        handleClusterStateChange('wafV2Enabled', eksValues.loadBalancer.enableWafv2);
       }
 
       if (eksValues.logging != null) {
-        const l = new EKSLogging();
-        l.enableApiServerLogs = eksValues.logging.enableApiServerLogs;
-        l.enableAuditLogs = eksValues.logging.enableAuditLogs;
-        l.enableAuthenticatorLogs = eksValues.logging.enableAuthenticatorLogs;
-        l.enableControllerManagerLogs =
-          eksValues.logging.enableControllerManagerLogs;
-        l.enableSchedulerLogs = eksValues.logging.enableSchedulerLogs;
-        setControlPlaneLogs(l);
+        const logging = {
+          enableApiServerLogs: eksValues.logging.enableApiServerLogs,
+          enableAuditLogs: eksValues.logging.enableAuditLogs,
+          enableAuthenticatorLogs: eksValues.logging.enableAuthenticatorLogs,
+          enableControllerManagerLogs: eksValues.logging.enableControllerManagerLogs,
+          enableSchedulerLogs: eksValues.logging.enableSchedulerLogs,
+        };
+        setControlPlaneLogs(logging);
       }
-      setGuardDutyEnabled(eksValues.enableGuardDuty);
-      setKmsEncryptionEnabled(eksValues.enableKmsEncryption);
+
+      handleClusterStateChange('guardDutyEnabled', eksValues.enableGuardDuty);
+      handleClusterStateChange('kmsEncryptionEnabled', eksValues.enableKmsEncryption);
     }
   }, [isExpanded, props.selectedClusterVersion]);
 
@@ -508,18 +514,18 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
       setStep(1)
       preflightChecks()
     }
-  }, [props.selectedClusterVersion, awsRegion]);
+  }, [props.selectedClusterVersion, clusterState]);
 
-  const proceedToProvision = async () => {
+  const proceedToProvision = async (): Promise<void> => {
     setShowEmailMessage(true)
     markStepStarted("requested-quota-increase")
     setStep(2)
   }
-  const requestQuotasAndProvision = async () => {
+  const requestQuotasAndProvision = async (): Promise<void> => {
     await requestQuotaIncrease()
     await createCluster()
   }
-  const requestQuotaIncrease = async () => {
+  const requestQuotaIncrease = async (): Promise<void> => {
 
     try {
       setIsLoading(true);
@@ -545,13 +551,11 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
 
       setIsLoading(false)
     } catch (err) {
-      console.log(err)
       setIsLoading(false)
     }
 
   }
-
-  const preflightChecks = async () => {
+  const preflightChecks = async (): Promise<void> => {
 
     try {
       setIsLoading(true);
@@ -560,16 +564,9 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
       setPreflightError("");
       setShowEmailMessage(false)
 
+      const contract = createClusterObj();
       var data = new PreflightCheckRequest({
-        projectId: BigInt(currentProject.id),
-        cloudProvider: EnumCloudProvider.AWS,
-        cloudProviderCredentialsId: props.credentialId,
-        preflightValues: {
-          case: "eksPreflightValues",
-          value: new EKSPreflightValues({
-            region: awsRegion,
-          })
-        }
+        contract: contract,
       });
       const preflightDataResp = await api.preflightCheck(
         "<token>", data,
@@ -592,7 +589,6 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
       setQuotaIncrease(quotas)
       // If none of the checks have a message, set setPreflightFailed to false
       if (hasMessage) {
-        setShowPreflightModal(true)
         showIntercomWithMessage({ message: "I am running into an issue provisioning a cluster." });
         markStepStarted("provisioning-failed", errors);
       }
@@ -609,7 +605,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
     }
 
   }
-  const renderAdvancedSettings = () => {
+  const renderAdvancedSettings = (): JSX.Element => {
     return (
       <>
         {
@@ -630,9 +626,9 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                 <Input
                   width="350px"
                   type="string"
-                  value={clusterVersion}
+                  value={clusterState.clusterVersion}
                   disabled={true}
-                  setValue={(x: string) => setCidrRangeServices(x)}
+                  setValue={(x: string) => handleClusterStateChange("clusterVersion", x)}
                   label="Cluster version (only shown to porter.run emails)"
                 />
 
@@ -642,22 +638,21 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                 options={machineTypeOptions}
                 width="350px"
                 disabled={isReadOnly}
-                value={machineType}
-                setValue={setMachineType}
+                value={clusterState.machineType}
+                setValue={(x: string) => handleClusterStateChange("machineType", x)}
                 label="Machine type"
               />
               <Spacer y={1} />
               <Input
                 width="350px"
                 type="number"
-                disabled={isReadOnly}
-                value={maxInstances.toString()}
+                disabled={isReadOnly || isLoading}
+                value={clusterState.maxInstances.toString()}
                 setValue={(x: string) => {
-                  const num = parseInt(x, 10)
-                  if (num == undefined) {
-                    return
+                  const num = parseInt(x, 10);
+                  if (!isNaN(num)) {
+                    handleClusterStateChange('maxInstances', num);
                   }
-                  setMaxInstances(num)
                 }}
                 label="Maximum number of application nodes"
                 placeholder="ex: 1"
@@ -666,14 +661,14 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
               <Input
                 width="350px"
                 type="number"
-                disabled={isReadOnly}
-                value={minInstances.toString()}
+                disabled={isReadOnly || isLoading}
+                value={clusterState.minInstances.toString()}
                 setValue={(x: string) => {
                   const num = parseInt(x, 10)
-                  if (num == undefined) {
+                  if (num === undefined) {
                     return
                   }
-                  setMinInstances(num)
+                  handleClusterStateChange('minInstances', num);
                 }}
                 label="Minimum number of application nodes. If set to 0, no applications will be deployed."
                 placeholder="ex: 1"
@@ -682,9 +677,9 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
               <Input
                 width="350px"
                 type="string"
-                value={cidrRangeVPC}
-                disabled={props.clusterId}
-                setValue={(x: string) => setCidrRangeVPC(x)}
+                value={clusterState.cidrRangeVPC}
+                disabled={props.clusterId !== undefined || isLoading}
+                setValue={(x: string) => handleClusterStateChange('cidrRangeVPC', x)}
                 label="CIDR range for AWS VPC"
                 placeholder="ex: 10.78.0.0/16"
               />
@@ -692,13 +687,13 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
               <Input
                 width="350px"
                 type="string"
-                value={cidrRangeServices}
-                disabled={props.clusterId}
-                setValue={(x: string) => setCidrRangeServices(x)}
+                value={clusterState.cidrRangeServices}
+                disabled={props.clusterId !== undefined || isLoading}
+                setValue={(x: string) => handleClusterStateChange('cidrRangeServices', x)}
                 label="CIDR range for Kubernetes internal services"
                 placeholder="ex: 172.20.0.0/16"
               />
-              {!currentProject.simplified_view_enabled && (
+              {(currentProject && !currentProject.simplified_view_enabled) && (
                 <>
                   <Spacer y={1} />
                   <Checkbox
@@ -808,20 +803,19 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
 
                   <Spacer y={1} />
                   <Checkbox
-                    checked={loadBalancerType}
+                    checked={clusterState.loadBalancerType}
                     disabled={isReadOnly}
                     toggleChecked={() => {
-                      if (loadBalancerType) {
-                        setWildCardDomain("");
-                        setIPAllowList("");
-                        setwafV2ARN("");
-                        setAwsTags("");
-                        seCertificateARN("");
-                        setWaf2Enabled(false);
-                        //setAccessS3Logs(false);
+                      if (clusterState.loadBalancerType) {
+                        handleClusterStateChange("wildCardDomain", "");
+                        handleClusterStateChange("IPAllowList", "");
+                        handleClusterStateChange("wafV2ARN", "");
+                        handleClusterStateChange("awsTags", "");
+                        handleClusterStateChange("certificateARN", "");
+                        handleClusterStateChange("wafV2Enabled", false);
                       }
 
-                      setLoadBalancerType(!loadBalancerType);
+                      handleClusterStateChange("loadBalancerType", !clusterState.loadBalancerType);
                     }}
                     disabledTooltip={
                       "Wait for provisioning to complete before editing this field."
@@ -830,14 +824,14 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                     <Text color="helper">Set Load Balancer Type to ALB</Text>
                   </Checkbox>
                   <Spacer y={1} />
-                  {loadBalancerType && (
+                  {clusterState.loadBalancerType && (
                     <>
                       <FlexCenter>
                         <Input
                           width="350px"
                           disabled={isReadOnly}
-                          value={wildCardDomain}
-                          setValue={(x: string) => setWildCardDomain(x)}
+                          value={clusterState.wildCardDomain}
+                          setValue={(x: string) => handleClusterStateChange("wildCardDomain", x)}
                           label="Wildcard domain"
                           placeholder="user-2.porter.run"
                         />
@@ -852,10 +846,10 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                         </Wrapper>
                       </FlexCenter>
 
-                      {validateInput(wildCardDomain) && (
+                      {validateInput(clusterState.wildCardDomain) && (
                         <ErrorInLine>
                           <i className="material-icons">error</i>
-                          {validateInput(wildCardDomain)}
+                          {validateInput(clusterState.wildCardDomain)}
                         </ErrorInLine>
                       )}
                       <Spacer y={1} />
@@ -865,8 +859,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                           <Input
                             width="350px"
                             disabled={isReadOnly}
-                            value={IPAllowList}
-                            setValue={(x: string) => setIPAllowList(x)}
+                            value={clusterState.IPAllowList}
+                            setValue={(x: string) => handleClusterStateChange("IPAllowList", x)}
                             label="IP Allow List"
                             placeholder="160.72.72.58/32,160.72.72.59/32"
                           />
@@ -881,7 +875,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                           </Wrapper>
                         </>
                       </FlexCenter>
-                      {validateIPInput(IPAllowList) && (
+                      {validateIPInput(clusterState.IPAllowList) && (
                         <ErrorInLine>
                           <i className="material-icons">error</i>
                           {"Needs to be Comma Separated Valid IP addresses"}
@@ -892,8 +886,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                       <Input
                         width="350px"
                         disabled={isReadOnly}
-                        value={certificateARN}
-                        setValue={(x: string) => seCertificateARN(x)}
+                        value={clusterState.certificateARN}
+                        setValue={(x: string) => handleClusterStateChange("certificateARN", x)}
                         label="Certificate ARN"
                         placeholder="arn:aws:acm:REGION:ACCOUNT_ID:certificate/ACM_ID"
                       />
@@ -904,8 +898,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                           <Input
                             width="350px"
                             disabled={isReadOnly}
-                            value={awsTags}
-                            setValue={(x: string) => setAwsTags(x)}
+                            value={clusterState.awsTags}
+                            setValue={(x: string) => handleClusterStateChange("awsTags", x)}
                             label="AWS Tags"
                             placeholder="costcenter=1,environment=10,project=32"
                           />
@@ -920,7 +914,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                           </Wrapper>
                         </>
                       </FlexCenter>
-                      {validateTags(awsTags) && (
+                      {validateTags(clusterState.awsTags) && (
                         <ErrorInLine>
                           <i className="material-icons">error</i>
                           {"Needs to be Comma Separated Valid Tags"}
@@ -943,13 +937,13 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
             </Checkbox> */}
                       {/*<Spacer y={1} />*/}
                       <Checkbox
-                        checked={wafV2Enabled}
+                        checked={clusterState.wafV2Enabled}
                         disabled={isReadOnly}
                         toggleChecked={() => {
-                          if (wafV2Enabled) {
-                            setwafV2ARN("");
+                          if (clusterState.wafV2Enabled) {
+                            handleClusterStateChange("wafV2ARN", "")
                           }
-                          setWaf2Enabled(!wafV2Enabled);
+                          handleClusterStateChange("wafV2Enabled", !clusterState.wafV2Enabled);
                         }}
                         disabledTooltip={
                           "Wait for provisioning to complete before editing this field."
@@ -957,7 +951,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                       >
                         <Text color="helper">WAFv2 Enabled</Text>
                       </Checkbox>
-                      {wafV2Enabled && (
+                      {clusterState.wafV2Enabled && (
                         <>
                           <Spacer y={1} />
 
@@ -968,8 +962,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                                 type="string"
                                 label="WAFv2 ARN"
                                 disabled={isReadOnly}
-                                value={wafV2ARN}
-                                setValue={(x: string) => setwafV2ARN(x)}
+                                value={clusterState.wafV2ARN}
+                                setValue={(x: string) => handleClusterStateChange("wafV2ARN", x)}
                                 placeholder="arn:aws:wafv2:REGION:ACCOUNT_ID:regional/webacl/ACL_NAME/RULE_ID"
                               />
                               <Wrapper>
@@ -984,7 +978,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                             </>
                           </FlexCenter>
 
-                          {(wafV2ARN == undefined || wafV2ARN?.length == 0) && (
+                          {(clusterState.wafV2ARN === undefined || clusterState.wafV2ARN?.length === 0) && (
                             <ErrorInLine>
                               <i className="material-icons">error</i>
                               {"Required if WafV2 is enabled"}
@@ -997,10 +991,10 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                   )}
                   <FlexCenter>
                     <Checkbox
-                      checked={guardDutyEnabled}
+                      checked={clusterState.guardDutyEnabled}
                       disabled={isReadOnly}
                       toggleChecked={() => {
-                        setGuardDutyEnabled(!guardDutyEnabled);
+                        handleClusterStateChange("guardDutyEnabled", !clusterState.guardDutyEnabled);
                       }}
                       disabledTooltip={
                         "Wait for provisioning to complete before editing this field."
@@ -1022,12 +1016,12 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                   <Spacer y={1} />
                   <FlexCenter>
                     <Checkbox
-                      checked={kmsEncryptionEnabled}
+                      checked={clusterState.kmsEncryptionEnabled}
                       disabled={isReadOnly || currentCluster != null}
                       toggleChecked={() => {
-                        setKmsEncryptionEnabled(!kmsEncryptionEnabled);
+                        handleClusterStateChange("kmsEncryptionEnabled", !clusterState.kmsEncryptionEnabled);
                       }}
-                      disabledTooltip={kmsEncryptionEnabled ? "KMS encryption can never be disabled." :
+                      disabledTooltip={clusterState.kmsEncryptionEnabled ? "KMS encryption can never be disabled." :
                         "Encryption is only supported at cluster creation."
                       }
                     >
@@ -1044,7 +1038,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                       />
                     </Checkbox>
                   </FlexCenter>
-                  {kmsEncryptionEnabled && (
+                  {clusterState.kmsEncryptionEnabled && (
                     <ErrorInLine>
                       <i className="material-icons">error</i>
                       {
@@ -1062,12 +1056,16 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
     );
   };
 
-  const dismissPreflight = () => {
+  const dismissPreflight = async (): Promise<void> => {
     setShowHelpMessage(false);
-    preflightChecks();
+    try {
+      await preflightChecks();
+    } catch (err) {
+
+    }
   }
 
-  const renderForm = () => {
+  const renderForm = (): JSX.Element => {
     // Render simplified form if initial create
     if (!props.clusterId) {
       return (
@@ -1082,10 +1080,10 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                 options={regionOptions}
                 width="350px"
                 disabled={isReadOnly || isLoading}
-                value={awsRegion}
+                value={clusterState.awsRegion}
                 scrollBuffer={true}
                 dropdownMaxHeight="240px"
-                setActiveValue={setAwsRegion}
+                setActiveValue={(x: string) => handleClusterStateChange("awsRegion", x)}
                 label="📍 AWS region" />
               <>
                 {
@@ -1112,7 +1110,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                     <>
                       {(showHelpMessage && currentProject?.quota_increase) ? <>
                         <Text color="helper">
-                          Your account currently is blocked from provisioning in {awsRegion} due to a quota limit imposed by AWS. Either change the region or request to increase quotas.
+                          Your account currently is blocked from provisioning in {clusterState.awsRegion} due to a quota limit imposed by AWS. Either change the region or request to increase quotas.
                         </Text>
                         <Spacer y={.5} />
                         <Text color="helper">
@@ -1138,7 +1136,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
 
                       </> : (
                         <><Text color="helper">
-                          Your account currently is blocked from provisioning in {awsRegion} due to a quota limit imposed by AWS. Either change the region or request to increase quotas.
+                          Your account currently is blocked from provisioning in {clusterState.awsRegion} due to a quota limit imposed by AWS. Either change the region or request to increase quotas.
                         </Text><Spacer y={.5} /><Button
                           disabled={isLoading}
                           onClick={preflightChecks}
@@ -1184,10 +1182,10 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
           options={regionOptions}
           width="350px"
           disabled={isReadOnly || true}
-          value={awsRegion}
+          value={clusterState.awsRegion}
           scrollBuffer={true}
           dropdownMaxHeight="240px"
-          setActiveValue={setAwsRegion}
+          setActiveValue={(x: string) => handleClusterStateChange("awsRegion", x)}
           label="📍 AWS region" />
         {renderAdvancedSettings()}
       </StyledForm>
@@ -1227,71 +1225,71 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
 export default withRouter(ProvisionerSettings);
 
 const ExpandHeader = styled.div<{ isExpanded: boolean }>`
-              display: flex;
-              align-items: center;
-              cursor: pointer;
+      display: flex;
+      align-items: center;
+      cursor: pointer;
   > i {
-                margin - right: 7px;
-              margin-left: -7px;
-              transform: ${(props) =>
+        margin - right: 7px;
+      margin-left: -7px;
+      transform: ${(props) =>
     props.isExpanded ? "rotate(0deg)" : "rotate(-90deg)"};
-              transition: transform 0.1s ease;
+      transition: transform 0.1s ease;
   }
-              `;
+      `;
 
 const StyledForm = styled.div`
-              position: relative;
-              padding: 30px 30px 25px;
-              border-radius: 5px;
-              background: ${({ theme }) => theme.fg};
-              border: 1px solid #494b4f;
-              font-size: 13px;
-              margin-bottom: 30px;
-              `;
+      position: relative;
+      padding: 30px 30px 25px;
+      border-radius: 5px;
+      background: ${({ theme }) => theme.fg};
+      border: 1px solid #494b4f;
+      font-size: 13px;
+      margin-bottom: 30px;
+      `;
 
 const FlexCenter = styled.div`
-              display: flex;
-              align-items: center;
-              gap: 3px;
-              `;
+      display: flex;
+      align-items: center;
+      gap: 3px;
+      `;
 const Wrapper = styled.div`
-              transform: translateY(+13px);
-              `;
+      transform: translateY(+13px);
+      `;
 
 const ErrorInLine = styled.div`
-              display: flex;
-              align-items: center;
-              font-size: 13px;
-              color: #ff3b62;
-              margin-top: 10px;
+      display: flex;
+      align-items: center;
+      font-size: 13px;
+      color: #ff3b62;
+      margin-top: 10px;
 
   > i {
-                font - size: 18px;
-              margin-right: 5px;
+        font - size: 18px;
+      margin-right: 5px;
   }
-              `;
+      `;
 
 const CheckItemContainer = styled.div`
-  display: flex;
-  flex-direction: column;
-  border: 1px solid ${props => props.theme.border};
-  border-radius: 5px;
-  font-size: 13px;
-  width: 100%;
-  margin-bottom: 10px;
-  padding-left: 10px;
-  cursor: ${props => (props.hasMessage ? 'pointer' : 'default')};
-  background: ${props => props.theme.clickable.bg};
-
-`;
+      display: flex;
+      flex-direction: column;
+      border: 1px solid ${props => props.theme.border};
+      border-radius: 5px;
+      font-size: 13px;
+      width: 100%;
+      margin-bottom: 10px;
+      padding-left: 10px;
+      cursor: ${props => (props.hasMessage ? 'pointer' : 'default')};
+      background: ${props => props.theme.clickable.bg};
+
+      `;
 
 const CheckItemTop = styled.div`
-  display: flex;
-  align-items: center;
-  padding: 10px;
-  background: ${props => props.theme.clickable.bg};
-`;
+      display: flex;
+      align-items: center;
+      padding: 10px;
+      background: ${props => props.theme.clickable.bg};
+      `;
 
 const StatusIcon = styled.img`
-height: 14px;
-`;
+      height: 14px;
+      `;