Преглед изворни кода

Merge branch 'nico/credentials-preview-aws' of github.com:porter-dev/porter into nico/new-onboarding-flow

jnfrati пре 4 година
родитељ
комит
87636dd8d8

+ 8 - 0
dashboard/src/main/home/onboarding/state/StateHandler.ts

@@ -77,6 +77,10 @@ export const StateHandler = proxy({
       };
       };
     },
     },
     saveRegistryProvider: (provider: string) => {
     saveRegistryProvider: (provider: string) => {
+      if (provider === StateHandler.connected_registry?.provider) {
+        return;
+      }
+
       StateHandler.connected_registry = {
       StateHandler.connected_registry = {
         skip: false,
         skip: false,
         provider: provider as any,
         provider: provider as any,
@@ -101,6 +105,10 @@ export const StateHandler = proxy({
       };
       };
     },
     },
     saveResourceProvisioningProvider: (provider: string) => {
     saveResourceProvisioningProvider: (provider: string) => {
+      if (provider === StateHandler.provision_resources?.provider) {
+        return;
+      }
+
       StateHandler.provision_resources = {
       StateHandler.provision_resources = {
         skip: provider === "external",
         skip: provider === "external",
         provider: provider as any,
         provider: provider as any,

+ 0 - 2
dashboard/src/main/home/onboarding/state/StepHandler.ts

@@ -72,7 +72,6 @@ const flow: FlowType = {
           execute: {
           execute: {
             on: {
             on: {
               continue: "saveRegistryCredentials",
               continue: "saveRegistryCredentials",
-              go_back: "clearRegistryProvider",
             },
             },
           },
           },
         },
         },
@@ -143,7 +142,6 @@ const flow: FlowType = {
           execute: {
           execute: {
             on: {
             on: {
               continue: "saveResourceProvisioningCredentials",
               continue: "saveResourceProvisioningCredentials",
-              go_back: "clearResourceProvisioningProvider",
             },
             },
           },
           },
         },
         },

+ 1 - 1
dashboard/src/main/home/onboarding/state/index.ts

@@ -101,7 +101,7 @@ const decompressState = (prev_state: any) => {
     skip: state.skip_registry_connection,
     skip: state.skip_registry_connection,
     provider: state.registry_connection_provider,
     provider: state.registry_connection_provider,
     credentials: {
     credentials: {
-      id: state?.registry_connection_data?.id,
+      id: state?.registry_connection_credential_id,
     },
     },
     settings: {
     settings: {
       registry_connection_id: state?.registry_connection_id,
       registry_connection_id: state?.registry_connection_id,

+ 138 - 41
dashboard/src/main/home/onboarding/steps/ConnectRegistry/forms/_AWSRegistryForm.tsx

@@ -3,7 +3,7 @@ import SelectRow from "components/form-components/SelectRow";
 import Helper from "components/form-components/Helper";
 import Helper from "components/form-components/Helper";
 import SaveButton from "components/SaveButton";
 import SaveButton from "components/SaveButton";
 import { AWSRegistryConfig } from "main/home/onboarding/types";
 import { AWSRegistryConfig } from "main/home/onboarding/types";
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
 import styled from "styled-components";
 import styled from "styled-components";
 import api from "shared/api";
 import api from "shared/api";
 import { useSnapshot } from "valtio";
 import { useSnapshot } from "valtio";
@@ -11,6 +11,7 @@ import { OFState } from "../../../state/index";
 import IntegrationCategories from "main/home/integrations/IntegrationCategories";
 import IntegrationCategories from "main/home/integrations/IntegrationCategories";
 import { StateHandler } from "main/home/onboarding/state/StateHandler";
 import { StateHandler } from "main/home/onboarding/state/StateHandler";
 import RegistryImageList from "main/home/onboarding/components/RegistryImageList";
 import RegistryImageList from "main/home/onboarding/components/RegistryImageList";
+import Loading from "components/Loading";
 
 
 const regionOptions = [
 const regionOptions = [
   { value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
   { value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
@@ -35,14 +36,61 @@ const regionOptions = [
   { value: "sa-east-1", label: "South America (São Paulo) sa-east-1" },
   { value: "sa-east-1", label: "South America (São Paulo) sa-east-1" },
 ];
 ];
 
 
+const readableDate = (s: string) => {
+  const ts = new Date(s);
+  const date = ts.toLocaleDateString();
+  const time = ts.toLocaleTimeString([], {
+    hour: "numeric",
+    minute: "2-digit",
+  });
+  return `${time} on ${date}`;
+};
+
 export const CredentialsForm: React.FC<{
 export const CredentialsForm: React.FC<{
   nextFormStep: (data: Partial<AWSRegistryConfig>) => void;
   nextFormStep: (data: Partial<AWSRegistryConfig>) => void;
   project: any;
   project: any;
 }> = ({ nextFormStep, project }) => {
 }> = ({ nextFormStep, project }) => {
+  const snap = useSnapshot(OFState);
   const [accessId, setAccessId] = useState("");
   const [accessId, setAccessId] = useState("");
   const [secretKey, setSecretKey] = useState("");
   const [secretKey, setSecretKey] = useState("");
   const [buttonStatus, setButtonStatus] = useState("");
   const [buttonStatus, setButtonStatus] = useState("");
   const [awsRegion, setAWSRegion] = useState("us-east-1");
   const [awsRegion, setAWSRegion] = useState("us-east-1");
+  const [showForm, setShowForm] = useState(false);
+  const [lastConnectedAccount, setLastConnectedAccount] = useState(null);
+  const [isLoading, setIsLoading] = useState(true);
+
+  useEffect(() => {
+    api
+      .getAWSIntegration("<token>", {}, { project_id: project.id })
+      .then((res) => {
+        let integrations = res.data;
+        if (!Array.isArray(integrations) || !integrations.length) {
+          setShowForm(true);
+          return;
+        }
+
+        let lastUsed = integrations.find((i) => {
+          return (
+            i.id === snap.StateHandler?.connected_registry?.credentials?.id
+          );
+        });
+
+        if (!lastUsed) {
+          setShowForm(true);
+          return;
+        }
+
+        setLastConnectedAccount(lastUsed);
+        setShowForm(false);
+      })
+      .catch((err) => {
+        setShowForm(true);
+        console.error(err);
+      })
+      .finally(() => {
+        setIsLoading(false);
+      });
+  }, []);
 
 
   const validate = () => {
   const validate = () => {
     if (!accessId) {
     if (!accessId) {
@@ -82,56 +130,103 @@ export const CredentialsForm: React.FC<{
         }
         }
       );
       );
 
 
-      nextFormStep({
-        credentials: {
-          id: res.data?.id,
-        },
-      });
+      continueToNextStep(res.data?.id);
     } catch (error) {
     } catch (error) {
       setButtonStatus("Something went wrong, please try again");
       setButtonStatus("Something went wrong, please try again");
     }
     }
   };
   };
 
 
+  const continueToNextStep = (integration_id: number) => {
+    nextFormStep({
+      credentials: {
+        id: integration_id,
+      },
+    });
+  };
+
+  if (isLoading) {
+    return <Loading />;
+  }
+
+  if (showForm) {
+    return (
+      <>
+        <InputRow
+          type="text"
+          value={accessId}
+          setValue={(x: string) => {
+            setAccessId(x);
+          }}
+          label="👤 AWS Access ID"
+          placeholder="ex: AKIAIOSFODNN7EXAMPLE"
+          width="100%"
+          isRequired={true}
+        />
+        <InputRow
+          type="password"
+          value={secretKey}
+          setValue={(x: string) => {
+            setSecretKey(x);
+          }}
+          label="🔒 AWS Secret Key"
+          placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
+          width="100%"
+          isRequired={true}
+        />
+        <SelectRow
+          options={regionOptions}
+          width="100%"
+          scrollBuffer={true}
+          value={awsRegion}
+          dropdownMaxHeight="240px"
+          setActiveValue={(x: string) => {
+            setAWSRegion(x);
+          }}
+          label="📍 AWS Region"
+        />
+        <Br />
+        <SaveButton
+          text="Continue"
+          disabled={false}
+          onClick={submit}
+          makeFlush={true}
+          clearPosition={true}
+          status={buttonStatus}
+          statusPosition={"right"}
+        />
+      </>
+    );
+  }
+
   return (
   return (
     <>
     <>
-      <InputRow
-        type="text"
-        value={accessId}
-        setValue={(x: string) => {
-          setAccessId(x);
-        }}
-        label="👤 AWS Access ID"
-        placeholder="ex: AKIAIOSFODNN7EXAMPLE"
-        width="100%"
-        isRequired={true}
-      />
-      <InputRow
-        type="password"
-        value={secretKey}
-        setValue={(x: string) => {
-          setSecretKey(x);
-        }}
-        label="🔒 AWS Secret Key"
-        placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
-        width="100%"
-        isRequired={true}
-      />
-      <SelectRow
-        options={regionOptions}
-        width="100%"
-        scrollBuffer={true}
-        value={awsRegion}
-        dropdownMaxHeight="240px"
-        setActiveValue={(x: string) => {
-          setAWSRegion(x);
-        }}
-        label="📍 AWS Region"
+      <div>
+        Last connected account:
+        <div>
+          <b>ARN: </b>
+          {lastConnectedAccount?.aws_arn}
+        </div>
+        <div>
+          <b>Connected on:</b> {readableDate(lastConnectedAccount?.created_at)}
+        </div>
+      </div>
+      <Br />
+      <SaveButton
+        text="Connect another account"
+        disabled={false}
+        onClick={() => setShowForm(true)}
+        makeFlush={true}
+        clearPosition={true}
+        status={""}
+        statusPosition={"right"}
       />
       />
       <Br />
       <Br />
+      <b>Or</b>
+      <Br />
       <SaveButton
       <SaveButton
-        text="Continue"
+        text="Continue with current account"
         disabled={false}
         disabled={false}
-        onClick={submit}
+        onClick={() => continueToNextStep(lastConnectedAccount?.id)}
         makeFlush={true}
         makeFlush={true}
         clearPosition={true}
         clearPosition={true}
         status={buttonStatus}
         status={buttonStatus}
@@ -194,7 +289,9 @@ export const SettingsForm: React.FC<{
 
 
   return (
   return (
     <>
     <>
-      <Helper>Provide a name for Porter to use when displaying your registry.</Helper>
+      <Helper>
+        Provide a name for Porter to use when displaying your registry.
+      </Helper>
       <InputRow
       <InputRow
         type="text"
         type="text"
         value={registryName}
         value={registryName}

+ 125 - 23
dashboard/src/main/home/onboarding/steps/ConnectRegistry/forms/_GCPRegistryForm.tsx

@@ -1,23 +1,71 @@
 import Helper from "components/form-components/Helper";
 import Helper from "components/form-components/Helper";
 import InputRow from "components/form-components/InputRow";
 import InputRow from "components/form-components/InputRow";
 import UploadArea from "components/form-components/UploadArea";
 import UploadArea from "components/form-components/UploadArea";
+import Loading from "components/Loading";
 import SaveButton from "components/SaveButton";
 import SaveButton from "components/SaveButton";
 import RegistryImageList from "main/home/onboarding/components/RegistryImageList";
 import RegistryImageList from "main/home/onboarding/components/RegistryImageList";
 import { OFState } from "main/home/onboarding/state";
 import { OFState } from "main/home/onboarding/state";
 import { StateHandler } from "main/home/onboarding/state/StateHandler";
 import { StateHandler } from "main/home/onboarding/state/StateHandler";
 import { GCPRegistryConfig } from "main/home/onboarding/types";
 import { GCPRegistryConfig } from "main/home/onboarding/types";
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
 import api from "shared/api";
 import api from "shared/api";
 import styled from "styled-components";
 import styled from "styled-components";
 import { useSnapshot } from "valtio";
 import { useSnapshot } from "valtio";
 
 
+const readableDate = (s: string) => {
+  const ts = new Date(s);
+  const date = ts.toLocaleDateString();
+  const time = ts.toLocaleTimeString([], {
+    hour: "numeric",
+    minute: "2-digit",
+  });
+  return `${time} on ${date}`;
+};
+
 export const CredentialsForm: React.FC<{
 export const CredentialsForm: React.FC<{
   nextFormStep: (data: Partial<GCPRegistryConfig>) => void;
   nextFormStep: (data: Partial<GCPRegistryConfig>) => void;
   project: any;
   project: any;
 }> = ({ nextFormStep, project }) => {
 }> = ({ nextFormStep, project }) => {
+  const snap = useSnapshot(OFState);
   const [projectId, setProjectId] = useState("");
   const [projectId, setProjectId] = useState("");
   const [serviceAccountKey, setServiceAccountKey] = useState("");
   const [serviceAccountKey, setServiceAccountKey] = useState("");
   const [buttonStatus, setButtonStatus] = useState("");
   const [buttonStatus, setButtonStatus] = useState("");
+  const [showForm, setShowForm] = useState(false);
+  const [lastConnectedAccount, setLastConnectedAccount] = useState(null);
+  const [isLoading, setIsLoading] = useState(true);
+
+  useEffect(() => {
+    api
+      .getGCPIntegration("<token>", {}, { project_id: project.id })
+      .then((res) => {
+        let integrations = res.data;
+        if (!Array.isArray(integrations) || !integrations.length) {
+          setShowForm(true);
+          return;
+        }
+
+        let lastUsed = integrations.find((i) => {
+          return (
+            i.id === snap.StateHandler?.connected_registry?.credentials?.id
+          );
+        });
+
+        if (!lastUsed) {
+          setShowForm(true);
+          return;
+        }
+
+        setLastConnectedAccount(lastUsed);
+        setShowForm(false);
+      })
+      .catch((err) => {
+        setShowForm(true);
+        console.error(err);
+      })
+      .finally(() => {
+        setIsLoading(false);
+      });
+  }, []);
 
 
   const validate = () => {
   const validate = () => {
     if (!projectId) {
     if (!projectId) {
@@ -63,34 +111,88 @@ export const CredentialsForm: React.FC<{
     }
     }
   };
   };
 
 
+  const continueToNextStep = (integration_id: number) => {
+    nextFormStep({
+      credentials: {
+        id: integration_id,
+      },
+    });
+  };
+
+  if (isLoading) {
+    return <Loading />;
+  }
+
+  if (showForm) {
+    return (
+      <>
+        <InputRow
+          type="text"
+          value={projectId}
+          setValue={(x: string) => {
+            setProjectId(x);
+          }}
+          label="🏷️ GCP Project ID"
+          placeholder="ex: blindfold-ceiling-24601"
+          width="100%"
+          isRequired={true}
+        />
+
+        <Helper>Service account credentials for GCP permissions.</Helper>
+        <UploadArea
+          setValue={(x: any) => setServiceAccountKey(x)}
+          label="🔒 GCP Key Data (JSON)"
+          placeholder="Choose a file or drag it here."
+          width="100%"
+          height="100%"
+          isRequired={true}
+        />
+        <Br />
+        <SaveButton
+          text="Continue"
+          disabled={false}
+          onClick={submit}
+          makeFlush={true}
+          clearPosition={true}
+          status={buttonStatus}
+          statusPosition={"right"}
+        />
+      </>
+    );
+  }
   return (
   return (
     <>
     <>
-      <InputRow
-        type="text"
-        value={projectId}
-        setValue={(x: string) => {
-          setProjectId(x);
-        }}
-        label="🏷️ GCP Project ID"
-        placeholder="ex: blindfold-ceiling-24601"
-        width="100%"
-        isRequired={true}
-      />
-
-      <Helper>Service account credentials for GCP permissions.</Helper>
-      <UploadArea
-        setValue={(x: any) => setServiceAccountKey(x)}
-        label="🔒 GCP Key Data (JSON)"
-        placeholder="Choose a file or drag it here."
-        width="100%"
-        height="100%"
-        isRequired={true}
+      <div>
+        Last connected account:
+        <div>
+          <b>Project id: </b>
+          {lastConnectedAccount?.gcp_project_id}
+        </div>
+        <div>
+          <b>Service account email: </b>
+          {lastConnectedAccount?.gcp_sa_email}
+        </div>
+        <div>
+          <b>Connected on:</b> {readableDate(lastConnectedAccount?.created_at)}
+        </div>
+      </div>
+      <Br />
+      <SaveButton
+        text="Connect another account"
+        disabled={false}
+        onClick={() => setShowForm(true)}
+        makeFlush={true}
+        clearPosition={true}
+        status={""}
+        statusPosition={"right"}
       />
       />
       <Br />
       <Br />
+      <b>Or</b>
+      <Br />
       <SaveButton
       <SaveButton
-        text="Continue"
+        text="Continue with current account"
         disabled={false}
         disabled={false}
-        onClick={submit}
+        onClick={() => continueToNextStep(lastConnectedAccount?.id)}
         makeFlush={true}
         makeFlush={true}
         clearPosition={true}
         clearPosition={true}
         status={buttonStatus}
         status={buttonStatus}

+ 175 - 40
dashboard/src/main/home/onboarding/steps/ProvisionResources/forms/_AWSProvisionerForm.tsx

@@ -7,10 +7,11 @@ import {
   AWSProvisionerConfig,
   AWSProvisionerConfig,
   AWSRegistryConfig,
   AWSRegistryConfig,
 } from "main/home/onboarding/types";
 } from "main/home/onboarding/types";
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
 import api from "shared/api";
 import api from "shared/api";
 import { useSnapshot } from "valtio";
 import { useSnapshot } from "valtio";
 import { SharedStatus } from "./SharedStatus";
 import { SharedStatus } from "./SharedStatus";
+import Loading from "components/Loading";
 
 
 const regionOptions = [
 const regionOptions = [
   { value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
   { value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
@@ -35,14 +36,77 @@ const regionOptions = [
   { value: "sa-east-1", label: "South America (São Paulo) sa-east-1" },
   { value: "sa-east-1", label: "South America (São Paulo) sa-east-1" },
 ];
 ];
 
 
+const readableDate = (s: string) => {
+  const ts = new Date(s);
+  const date = ts.toLocaleDateString();
+  const time = ts.toLocaleTimeString([], {
+    hour: "numeric",
+    minute: "2-digit",
+  });
+  return `${time} on ${date}`;
+};
+
 export const CredentialsForm: React.FC<{
 export const CredentialsForm: React.FC<{
   nextFormStep: (data: Partial<AWSRegistryConfig>) => void;
   nextFormStep: (data: Partial<AWSRegistryConfig>) => void;
   project: any;
   project: any;
 }> = ({ nextFormStep, project }) => {
 }> = ({ nextFormStep, project }) => {
+  const snap = useSnapshot(OFState);
   const [accessId, setAccessId] = useState("");
   const [accessId, setAccessId] = useState("");
   const [secretKey, setSecretKey] = useState("");
   const [secretKey, setSecretKey] = useState("");
   const [awsRegion, setAWSRegion] = useState("us-east-1");
   const [awsRegion, setAWSRegion] = useState("us-east-1");
   const [buttonStatus, setButtonStatus] = useState("");
   const [buttonStatus, setButtonStatus] = useState("");
+  const [showForm, setShowForm] = useState(false);
+  const [lastConnectedAccount, setLastConnectedAccount] = useState(null);
+  const [isLoading, setIsLoading] = useState(true);
+
+  useEffect(() => {
+    api
+      .getAWSIntegration("<token>", {}, { project_id: project.id })
+      .then((res) => {
+        let integrations = res.data;
+        if (!Array.isArray(integrations) || !integrations.length) {
+          setShowForm(true);
+          return;
+        }
+
+        // DO NOT USE THE INTEGRATION ID FROM THE CONNECTED REGISTRY
+        integrations = integrations.filter((i) => {
+          return (
+            i.id !== snap.StateHandler?.connected_registry?.credentials?.id
+          );
+        });
+
+        // filter can change the type from integrations so just in case
+        // we check again that integrations is an array
+        if (!Array.isArray(integrations) || !integrations) {
+          setShowForm(true);
+          return;
+        }
+
+        integrations.sort((a, b) => b.id - a.id);
+
+        let lastUsed = integrations.find((i) => {
+          return (
+            i.id === snap.StateHandler?.provision_resources?.credentials?.id
+          );
+        });
+
+        if (!lastUsed) {
+          setShowForm(true);
+          return;
+        }
+
+        setLastConnectedAccount(lastUsed);
+        setShowForm(false);
+      })
+      .catch((err) => {
+        setShowForm(true);
+        console.error(err);
+      })
+      .finally(() => {
+        setIsLoading(false);
+      });
+  }, []);
 
 
   const validate = () => {
   const validate = () => {
     if (!accessId) {
     if (!accessId) {
@@ -83,56 +147,117 @@ export const CredentialsForm: React.FC<{
         }
         }
       );
       );
 
 
-      nextFormStep({
-        credentials: {
-          id: res.data?.id,
-        },
-      });
+      continueToNextStep(res.data?.id);
     } catch (error) {
     } catch (error) {
       setButtonStatus("Something went wrong, please try again");
       setButtonStatus("Something went wrong, please try again");
     }
     }
   };
   };
 
 
+  const continueToNextStep = (integration_id: number) => {
+    nextFormStep({
+      credentials: {
+        id: integration_id,
+      },
+    });
+  };
+
+  if (isLoading) {
+    return <Loading />;
+  }
+
+  if (showForm) {
+    return (
+      <div>
+        <InputRow
+          type="text"
+          value={accessId}
+          setValue={(x: string) => {
+            setAccessId(x);
+          }}
+          label="👤 AWS Access ID"
+          placeholder="ex: AKIAIOSFODNN7EXAMPLE"
+          width="100%"
+          isRequired={true}
+        />
+        <InputRow
+          type="password"
+          value={secretKey}
+          setValue={(x: string) => {
+            setSecretKey(x);
+          }}
+          label="🔒 AWS Secret Key"
+          placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
+          width="100%"
+          isRequired={true}
+        />
+        <SelectRow
+          options={regionOptions}
+          width="100%"
+          value={awsRegion}
+          scrollBuffer={true}
+          dropdownMaxHeight="240px"
+          setActiveValue={(x: string) => {
+            setAWSRegion(x);
+          }}
+          label="📍 AWS Region"
+        />
+        <Br />
+        {lastConnectedAccount && (
+          <CancelButton
+            text="Cancel"
+            disabled={false}
+            onClick={() => setShowForm(false)}
+            makeFlush={true}
+            clearPosition={true}
+            status={""}
+            statusPosition={"right"}
+            color={"#fc4976"}
+          />
+        )}
+
+        <SubmitButton
+          text="Continue"
+          disabled={false}
+          onClick={submit}
+          makeFlush={true}
+          clearPosition={true}
+          status={buttonStatus}
+          statusPosition={"right"}
+          disableLeftMargin={!lastConnectedAccount}
+        />
+      </div>
+    );
+  }
+
   return (
   return (
     <>
     <>
-      <InputRow
-        type="text"
-        value={accessId}
-        setValue={(x: string) => {
-          setAccessId(x);
-        }}
-        label="👤 AWS Access ID"
-        placeholder="ex: AKIAIOSFODNN7EXAMPLE"
-        width="100%"
-        isRequired={true}
-      />
-      <InputRow
-        type="password"
-        value={secretKey}
-        setValue={(x: string) => {
-          setSecretKey(x);
-        }}
-        label="🔒 AWS Secret Key"
-        placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
-        width="100%"
-        isRequired={true}
-      />
-      <SelectRow
-        options={regionOptions}
-        width="100%"
-        value={awsRegion}
-        scrollBuffer={true}
-        dropdownMaxHeight="240px"
-        setActiveValue={(x: string) => {
-          setAWSRegion(x);
-        }}
-        label="📍 AWS Region"
+      <div>
+        Last connected account:
+        <div>
+          <b>ARN: </b>
+          {lastConnectedAccount?.aws_arn}
+        </div>
+        <div>
+          <b>Connected on:</b> {readableDate(lastConnectedAccount?.created_at)}
+        </div>
+      </div>
+      <Br />
+      <SaveButton
+        text="Connect another account"
+        disabled={false}
+        onClick={() => setShowForm(true)}
+        makeFlush={true}
+        clearPosition={true}
+        status={""}
+        statusPosition={"right"}
       />
       />
       <Br />
       <Br />
+      <b>Or</b>
+      <Br />
       <SaveButton
       <SaveButton
-        text="Continue"
+        text="Continue with current account"
         disabled={false}
         disabled={false}
-        onClick={submit}
+        onClick={() => continueToNextStep(lastConnectedAccount?.id)}
         makeFlush={true}
         makeFlush={true}
         clearPosition={true}
         clearPosition={true}
         status={buttonStatus}
         status={buttonStatus}
@@ -315,3 +440,13 @@ const Br = styled.div`
   width: 100%;
   width: 100%;
   height: 15px;
   height: 15px;
 `;
 `;
+
+const CancelButton = styled(SaveButton)`
+  display: inline-block;
+`;
+
+const SubmitButton = styled(SaveButton)`
+  display: inline-block;
+  margin-left: ${(props: { disableLeftMargin: boolean }) =>
+    props.disableLeftMargin ? "" : "16px"};
+`;

+ 143 - 23
dashboard/src/main/home/onboarding/steps/ProvisionResources/forms/_GCPProvisionerForm.tsx

@@ -2,13 +2,14 @@ import Helper from "components/form-components/Helper";
 import InputRow from "components/form-components/InputRow";
 import InputRow from "components/form-components/InputRow";
 import SelectRow from "components/form-components/SelectRow";
 import SelectRow from "components/form-components/SelectRow";
 import UploadArea from "components/form-components/UploadArea";
 import UploadArea from "components/form-components/UploadArea";
+import Loading from "components/Loading";
 import SaveButton from "components/SaveButton";
 import SaveButton from "components/SaveButton";
 import { OFState } from "main/home/onboarding/state";
 import { OFState } from "main/home/onboarding/state";
 import {
 import {
   GCPProvisionerConfig,
   GCPProvisionerConfig,
   GCPRegistryConfig,
   GCPRegistryConfig,
 } from "main/home/onboarding/types";
 } from "main/home/onboarding/types";
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
 import api from "shared/api";
 import api from "shared/api";
 import styled from "styled-components";
 import styled from "styled-components";
 import { useSnapshot } from "valtio";
 import { useSnapshot } from "valtio";
@@ -41,13 +42,76 @@ const regionOptions = [
   { value: "us-west4", label: "us-west4" },
   { value: "us-west4", label: "us-west4" },
 ];
 ];
 
 
+const readableDate = (s: string) => {
+  const ts = new Date(s);
+  const date = ts.toLocaleDateString();
+  const time = ts.toLocaleTimeString([], {
+    hour: "numeric",
+    minute: "2-digit",
+  });
+  return `${time} on ${date}`;
+};
+
 export const CredentialsForm: React.FC<{
 export const CredentialsForm: React.FC<{
   nextFormStep: (data: Partial<GCPRegistryConfig>) => void;
   nextFormStep: (data: Partial<GCPRegistryConfig>) => void;
   project: any;
   project: any;
 }> = ({ nextFormStep, project }) => {
 }> = ({ nextFormStep, project }) => {
+  const snap = useSnapshot(OFState);
   const [projectId, setProjectId] = useState("");
   const [projectId, setProjectId] = useState("");
   const [serviceAccountKey, setServiceAccountKey] = useState("");
   const [serviceAccountKey, setServiceAccountKey] = useState("");
   const [buttonStatus, setButtonStatus] = useState("");
   const [buttonStatus, setButtonStatus] = useState("");
+  const [showForm, setShowForm] = useState(false);
+  const [lastConnectedAccount, setLastConnectedAccount] = useState(null);
+  const [isLoading, setIsLoading] = useState(true);
+
+  useEffect(() => {
+    api
+      .getGCPIntegration("<token>", {}, { project_id: project.id })
+      .then((res) => {
+        let integrations = res.data;
+        if (!Array.isArray(integrations) || !integrations.length) {
+          setShowForm(true);
+          return;
+        }
+
+        // DO NOT USE THE INTEGRATION ID FROM THE CONNECTED REGISTRY
+        integrations = integrations.filter((i) => {
+          return (
+            i.id !== snap.StateHandler?.connected_registry?.credentials?.id
+          );
+        });
+
+        // filter can change the type from integrations so just in case
+        // we check again that integrations is an array
+        if (!Array.isArray(integrations) || !integrations) {
+          setShowForm(true);
+          return;
+        }
+
+        integrations.sort((a, b) => b.id - a.id);
+
+        let lastUsed = integrations.find((i) => {
+          return (
+            i.id === snap.StateHandler?.provision_resources?.credentials?.id
+          );
+        });
+
+        if (!lastUsed) {
+          setShowForm(true);
+          return;
+        }
+
+        setLastConnectedAccount(lastUsed);
+        setShowForm(false);
+      })
+      .catch((err) => {
+        setShowForm(true);
+        console.error(err);
+      })
+      .finally(() => {
+        setIsLoading(false);
+      });
+  }, []);
 
 
   const validate = () => {
   const validate = () => {
     if (!projectId) {
     if (!projectId) {
@@ -92,33 +156,89 @@ export const CredentialsForm: React.FC<{
       setButtonStatus("Something went wrong, please try again");
       setButtonStatus("Something went wrong, please try again");
     }
     }
   };
   };
+
+  const continueToNextStep = (integration_id: number) => {
+    nextFormStep({
+      credentials: {
+        id: integration_id,
+      },
+    });
+  };
+
+  if (isLoading) {
+    return <Loading />;
+  }
+
+  if (showForm) {
+    return (
+      <>
+        <InputRow
+          type="text"
+          value={projectId}
+          setValue={(x: string) => {
+            setProjectId(x);
+          }}
+          label="🏷️ GCP Project ID"
+          placeholder="ex: blindfold-ceiling-24601"
+          width="100%"
+          isRequired={true}
+        />
+
+        <Helper>Service account credentials for GCP permissions.</Helper>
+        <UploadArea
+          setValue={(x: any) => setServiceAccountKey(x)}
+          label="🔒 GCP Key Data (JSON)"
+          placeholder="Choose a file or drag it here."
+          width="100%"
+          height="100%"
+          isRequired={true}
+        />
+        <SaveButton
+          text="Continue"
+          disabled={false}
+          onClick={submit}
+          makeFlush={true}
+          clearPosition={true}
+          status={buttonStatus}
+          statusPosition={"right"}
+        />
+      </>
+    );
+  }
+
   return (
   return (
     <>
     <>
-      <InputRow
-        type="text"
-        value={projectId}
-        setValue={(x: string) => {
-          setProjectId(x);
-        }}
-        label="🏷️ GCP Project ID"
-        placeholder="ex: blindfold-ceiling-24601"
-        width="100%"
-        isRequired={true}
-      />
-
-      <Helper>Service account credentials for GCP permissions.</Helper>
-      <UploadArea
-        setValue={(x: any) => setServiceAccountKey(x)}
-        label="🔒 GCP Key Data (JSON)"
-        placeholder="Choose a file or drag it here."
-        width="100%"
-        height="100%"
-        isRequired={true}
+      <div>
+        Last connected account:
+        <div>
+          <b>Project id: </b>
+          {lastConnectedAccount?.gcp_project_id}
+        </div>
+        <div>
+          <b>Service account email: </b>
+          {lastConnectedAccount?.gcp_sa_email}
+        </div>
+        <div>
+          <b>Connected on:</b> {readableDate(lastConnectedAccount?.created_at)}
+        </div>
+      </div>
+      <Br />
+      <SaveButton
+        text="Connect another account"
+        disabled={false}
+        onClick={() => setShowForm(true)}
+        makeFlush={true}
+        clearPosition={true}
+        status={""}
+        statusPosition={"right"}
       />
       />
+      <Br />
+      <b>Or</b>
+      <Br />
       <SaveButton
       <SaveButton
-        text="Continue"
+        text="Continue with current account"
         disabled={false}
         disabled={false}
-        onClick={submit}
+        onClick={() => continueToNextStep(lastConnectedAccount?.id)}
         makeFlush={true}
         makeFlush={true}
         clearPosition={true}
         clearPosition={true}
         status={buttonStatus}
         status={buttonStatus}