Przeglądaj źródła

Merge branch 'master' of https://github.com/porter-dev/porter into main

sunguroku 5 lat temu
rodzic
commit
b40683e1b1

+ 2 - 1
dashboard/src/components/image-selector/ImageSelector.tsx

@@ -62,7 +62,8 @@ export default class ImageSelector extends Component<PropsType, StateType> {
               if (err) {
                 errors.push(1);
               } else {
-
+                console.log(res.data);
+                res.data.sort((a: any, b: any) => (a.created_at > b.created_at) ? 1 : -1);
                 // Loop over found image repositories
                 let newImg = res.data.map((img: any) => {
                   if (this.props.selectedImageUrl === img.uri) {

+ 39 - 92
dashboard/src/main/home/Home.tsx

@@ -1,7 +1,6 @@
 import React, { Component } from 'react';
 import posthog from 'posthog-js';
 import styled from 'styled-components';
-import ReactModal from 'react-modal';
 
 import { Context } from '../../shared/Context';
 import api from '../../shared/api';
@@ -23,6 +22,7 @@ import Navbar from './navbar/Navbar';
 import ProvisionerStatus from './provisioner/ProvisionerStatus';
 import ProjectSettings from './project-settings/ProjectSettings';
 import ConfirmOverlay from '../../components/ConfirmOverlay';
+import Modal from './modals/Modal';
 
 type PropsType = {
   logOut: () => void,
@@ -385,40 +385,44 @@ export default class Home extends Component<PropsType, StateType> {
     let { currentModal, setCurrentModal, currentProject } = this.context;
     return (
       <StyledHome>
-        <ReactModal
-          isOpen={currentModal === 'ClusterInstructionsModal'}
-          onRequestClose={() => setCurrentModal(null, null)}
-          style={TallModalStyles}
-          ariaHideApp={false}
-        >
-          <ClusterInstructionsModal />
-        </ReactModal>
-        <ReactModal
-          isOpen={currentModal === 'UpdateClusterModal'}
-          onRequestClose={() => setCurrentModal(null, null)}
-          style={ProjectModalStyles}
-          ariaHideApp={false}
-        >
-          <UpdateClusterModal 
-            setRefreshClusters={(x: boolean) => this.setState({ forceRefreshClusters: x })} 
-          />
-        </ReactModal>
-        <ReactModal
-          isOpen={currentModal === 'IntegrationsModal'}
-          onRequestClose={() => setCurrentModal(null, null)}
-          style={SmallModalStyles}
-          ariaHideApp={false}
-        >
-          <IntegrationsModal />
-        </ReactModal>
-        <ReactModal
-          isOpen={currentModal === 'IntegrationsInstructionsModal'}
-          onRequestClose={() => setCurrentModal(null, null)}
-          style={TallModalStyles}
-          ariaHideApp={false}
-        >
-          <IntegrationsInstructionsModal />
-        </ReactModal>
+        {currentModal === 'ClusterInstructionsModal' &&
+          <Modal
+            onRequestClose={() => setCurrentModal(null, null)}
+            width='760px'
+            height='650px'
+          >
+            <ClusterInstructionsModal />
+          </Modal>
+        }
+        {currentModal === 'UpdateClusterModal' &&
+          <Modal
+            onRequestClose={() => setCurrentModal(null, null)}
+            width='565px'
+            height='275px'
+          >
+            <UpdateClusterModal 
+              setRefreshClusters={(x: boolean) => this.setState({ forceRefreshClusters: x })} 
+            />
+          </Modal>
+        }
+        {currentModal === 'IntegrationsModal' &&
+          <Modal
+            onRequestClose={() => setCurrentModal(null, null)}
+            width='760px'
+            height='725px'
+          >
+            <IntegrationsModal />
+          </Modal>
+        }
+        {currentModal === 'IntegrationsInstructionsModal' &&
+          <Modal
+            onRequestClose={() => setCurrentModal(null, null)}
+            width='760px'
+            height='650px'
+          >
+            <IntegrationsInstructionsModal />
+          </Modal>
+        }
 
         {this.renderSidebar()}
 
@@ -443,63 +447,6 @@ export default class Home extends Component<PropsType, StateType> {
 
 Home.contextType = Context;
 
-const SmallModalStyles = {
-  overlay: {
-    backgroundColor: 'rgba(0,0,0,0.6)',
-    zIndex: 2,
-  },
-  content: {
-    borderRadius: '7px',
-    border: 0,
-    width: '760px',
-    maxWidth: '80vw',
-    margin: '0 auto',
-    height: '425px',
-    top: 'calc(50% - 214px)',
-    backgroundColor: '#202227',
-    animation: 'floatInModal 0.5s 0s',
-    overflow: 'visible',
-  },
-};
-
-const ProjectModalStyles = {
-  overlay: {
-    backgroundColor: 'rgba(0,0,0,0.6)',
-    zIndex: 2,
-  },
-  content: {
-    borderRadius: '7px',
-    border: 0,
-    width: '565px',
-    maxWidth: '80vw',
-    margin: '0 auto',
-    height: '275px',
-    top: 'calc(50% - 160px)',
-    backgroundColor: '#202227',
-    animation: 'floatInModal 0.5s 0s',
-    overflow: 'visible',
-  },
-};
-
-const TallModalStyles = {
-  overlay: {
-    backgroundColor: 'rgba(0,0,0,0.6)',
-    zIndex: 2,
-  },
-  content: {
-    borderRadius: '7px',
-    border: 0,
-    width: '760px',
-    maxWidth: '80vw',
-    margin: '0 auto',
-    height: '650px',
-    top: 'calc(50% - 325px)',
-    backgroundColor: '#202227',
-    animation: 'floatInModal 0.5s 0s',
-    overflow: 'visible',
-  },
-};
-
 const ViewWrapper = styled.div`
   height: 100%;
   width: 100vw;

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

@@ -48,12 +48,8 @@ export default class Dashboard extends Component<PropsType, StateType> {
   }
 
   onShowProjectSettings = () => {
-    let { currentProject, setCurrentModal } = this.context;
     let { setCurrentView } = this.props;
-    setCurrentModal('UpdateProjectModal', { 
-      currentProject: currentProject,
-      setCurrentView: setCurrentView,
-    });
+    setCurrentView('project-settings');
   }
 
   render() {
@@ -73,12 +69,16 @@ export default class Dashboard extends Component<PropsType, StateType> {
               </Overlay>
             </DashboardIcon>
               <Title>{currentProject && currentProject.name}</Title>
-              <i
-                className="material-icons"
-                onClick={onShowProjectSettings}
-              >
-                more_vert
-              </i>
+              {this.context.currentProject.roles.filter((obj: any) => {
+                return obj.user_id === this.context.user.userId;
+              })[0].kind === 'admin' &&
+                <i
+                  className="material-icons"
+                  onClick={onShowProjectSettings}
+                >
+                  more_vert
+                </i>
+              }
             </TitleSection>
 
             <InfoSection>

+ 81 - 0
dashboard/src/main/home/modals/Modal.tsx

@@ -0,0 +1,81 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+type PropsType = {
+  onRequestClose: () => void,
+  width?: string,
+  height?: string,
+}
+
+type StateType = {
+}
+
+export default class Modal extends Component<PropsType, StateType> {
+  wrapperRef: any = React.createRef();
+
+  componentDidMount() {
+    document.addEventListener('mousedown', this.handleClickOutside.bind(this));
+  }
+
+  componentWillUnmount() {
+    document.removeEventListener('mousedown', this.handleClickOutside.bind(this));
+  }
+
+  handleClickOutside = (event: any) => {
+    if (this.wrapperRef && !this.wrapperRef.current.contains(event.target)) {
+      this.props.onRequestClose();
+    }
+  }
+
+  render() {
+    let { width, height } = this.props;
+    return (
+      <Overlay>
+        <StyledModal
+          ref={this.wrapperRef}
+          width={width}
+          height={height}
+        >
+          {this.props.children}
+        </StyledModal>
+      </Overlay>
+    );
+  }
+}
+
+const Overlay = styled.div`
+  position: absolute;
+  margin: 0;
+  padding: 0;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0,0,0,0.6);
+  z-index: 3;
+`;
+
+const StyledModal = styled.div`
+  position: absolute;
+  top: calc(50% - (${(props: { width?: string, height?: string }) => props.height ? props.height : '425px'} / 2));
+  left: calc(50% - (${(props: { width?: string, height?: string }) => props.width ? props.width : '760px'} / 2));
+  display: flex;
+  justify-content: center;
+  width: ${(props: { width?: string, height?: string }) => props.width ? props.width : '760px'};
+  max-width: 80vw;
+  height: ${(props: { width?: string, height?: string }) => props.height ? props.height : '425px'};
+  border-radius: 7px;
+  border: 0;
+  background-color: #202227;
+  overflow: visible;
+  padding: 25px 32px;
+  animation: floatInModal 0.5s 0s;
+  @keyframes floatInModal {
+    from {
+      opacity: 0; transform: translateY(30px);
+    }
+    to {
+      opacity: 1; transform: translateY(0px);
+    }
+  }
+`;

+ 3 - 6
dashboard/src/main/home/project-settings/InviteList.tsx

@@ -142,7 +142,7 @@ export default class InviteList extends Component<PropsType, StateType> {
               </MailTd>
               <LinkTd isTop={i === 0}>
               </LinkTd>
-              <Td isTop={i === 0}>
+              <Td isTop={i === 0} invis={true}>
                 <CopyButton
                   onClick={() => this.deleteInvite(i)}
                 >
@@ -341,10 +341,6 @@ const ShareLink = styled.input`
   }
 `;
 
-const Spacer = styled.div`
-  height: 24px;
-`;
-
 const Table = styled.table`
   width: 100%;
   border-spacing: 0px;
@@ -353,9 +349,10 @@ const Table = styled.table`
 `;
 
 const Td = styled.td`
+  visibility: ${(props: { isTop: boolean, invis?: boolean }) => props.invis ? 'hidden' : 'visible'};
   white-space: nowrap;
   padding: 20px 0px;
-  border-top: ${(props: {isTop: boolean}) => (props.isTop ? 'none' : '1px solid #ffffff55')};
+  border-top: ${(props: { isTop: boolean, invis?: boolean }) => (props.isTop ? 'none' : '1px solid #ffffff55')};
   &:last-child {
     padding-right: 16px;
   }

+ 0 - 11
dashboard/src/main/home/project-settings/ProjectSettings.tsx

@@ -31,17 +31,6 @@ export default class ProjectSettings extends Component<PropsType, StateType> {
     this.setState({ projectName: currentProject.name });
   }
 
-  renderTitle = () => {
-    let { currentProject } = this.context;
-    if (currentProject) {
-      return (
-        <>
-
-        </>
-      );
-    }
-  }
-
   renderTabContents = () => {
     if (this.state.currentTab === 'manage-access') {
       return <InviteList />;

+ 30 - 7
dashboard/src/main/home/provisioner/AWSFormSection.tsx

@@ -7,6 +7,7 @@ import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
 import { ProjectType, InfraType } from '../../../shared/types';
 
+import SelectRow from '../../../components/values-form/SelectRow';
 import InputRow from '../../../components/values-form/InputRow';
 import Helper from '../../../components/values-form/Helper';
 import Heading from '../../../components/values-form/Heading';
@@ -34,10 +35,33 @@ const provisionOptions = [
   { value: 'eks', label: 'Elastic Kubernetes Service (EKS)' },
 ];
 
+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' },
+  { value: 'us-west-1', label: 'US West (N. California) us-west-1' },
+  { value: 'us-west-2', label: 'US West (Oregon) us-west-2' },
+  { value: 'af-south-1', label: 'Africa (Cape Town) af-south-1' },
+  { value: 'ap-east-1', label: 'Asia Pacific (Hong Kong)ap-east-1' },
+  { value: 'ap-south-1', label: 'Asia Pacific (Mumbai) ap-south-1' },
+  { value: 'ap-northeast-2', label: 'Asia Pacific (Seoul) ap-northeast-2' },
+  { value: 'ap-southeast-1', label: 'Asia Pacific (Singapore) ap-southeast-1' },
+  { value: 'ap-southeast-2', label: 'Asia Pacific (Sydney) ap-southeast-2' },
+  { value: 'ap-northeast-1', label: 'Asia Pacific (Tokyo) ap-northeast-1' },
+  { value: 'ca-central-1', label: 'Canada (Central) ca-central-1' },
+  { value: 'eu-central-1', label: 'Europe (Frankfurt) eu-central-1' },
+  { value: 'eu-west-1', label: 'Europe (Ireland) eu-west-1' },
+  { value: 'eu-west-2', label: 'Europe (London) eu-west-2' },
+  { value: 'eu-south-1', label: 'Europe (Milan) eu-south-1' },
+  { value: 'eu-west-3', label: 'Europe (Paris) eu-west-3' },
+  { value: 'eu-north-1', label: 'Europe (Stockholm) eu-north-1' },
+  { value: 'me-south-1', label: 'Middle East (Bahrain) me-south-1' },
+  { value: 'sa-east-1', label: 'South America (São Paulo) sa-east-1' },
+];
+
 // TODO: Consolidate across forms w/ HOC
 export default class AWSFormSection extends Component<PropsType, StateType> {
   state = {
-    awsRegion: '',
+    awsRegion: 'us-east-1',
     awsAccessId: '',
     awsSecretKey: '',
     selectedInfras: [...provisionOptions],
@@ -247,14 +271,13 @@ export default class AWSFormSection extends Component<PropsType, StateType> {
               Guide
             </GuideButton>
           </Heading>
-          <InputRow
-            type='text'
+          <SelectRow
+            options={regionOptions}
+            width='100%'
             value={awsRegion}
-            setValue={(x: string) => this.setState({ awsRegion: x })}
+            dropdownMaxHeight='240px'
+            setActiveValue={(x: string) => this.setState({ awsRegion: x })}
             label='📍 AWS Region'
-            placeholder='ex: us-east-2'
-            width='100%'
-            isRequired={true}
           />
           <InputRow
             type='text'

+ 216 - 17
dashboard/src/main/home/provisioner/GCPFormSection.tsx

@@ -2,7 +2,12 @@ import React, { Component } from 'react';
 import styled from 'styled-components';
 
 import close from '../../../assets/close.png';
+import { isAlphanumeric } from '../../../shared/common';
+import api from '../../../shared/api';
+import { Context } from '../../../shared/Context';
+import { ProjectType, InfraType } from '../../../shared/types';
 
+import SelectRow from '../../../components/values-form/SelectRow';
 import InputRow from '../../../components/values-form/InputRow';
 import Helper from '../../../components/values-form/Helper';
 import Heading from '../../../components/values-form/Heading';
@@ -11,6 +16,10 @@ import CheckboxList from '../../../components/values-form/CheckboxList';
 
 type PropsType = {
   setSelectedProvisioner: (x: string | null) => void,
+  handleError: () => void,
+  projectName: string,
+  setCurrentView: (x: string | null, data?: any) => void,
+  infras: InfraType[],
 };
 
 type StateType = {
@@ -18,19 +27,202 @@ type StateType = {
   gcpProjectId: string,
   gcpKeyData: string,
   selectedInfras: { value: string, label: string }[],
+  buttonStatus: string,
 };
 
-const dummyOptions = [
+const provisionOptions = [
   { value: 'gcr', label: 'Google Container Registry (GCR)' },
   { value: 'gke', label: 'Googke Kubernetes Engine (GKE)' },
 ];
 
+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' },
+  { value: 'us-west-1', label: 'US West (N. California) us-west-1' },
+  { value: 'us-west-2', label: 'US West (Oregon) us-west-2' },
+  { value: 'af-south-1', label: 'Africa (Cape Town) af-south-1' },
+  { value: 'ap-east-1', label: 'Asia Pacific (Hong Kong)ap-east-1' },
+  { value: 'ap-south-1', label: 'Asia Pacific (Mumbai) ap-south-1' },
+  { value: 'ap-northeast-2', label: 'Asia Pacific (Seoul) ap-northeast-2' },
+  { value: 'ap-southeast-1', label: 'Asia Pacific (Singapore) ap-southeast-1' },
+  { value: 'ap-southeast-2', label: 'Asia Pacific (Sydney) ap-southeast-2' },
+  { value: 'ap-northeast-1', label: 'Asia Pacific (Tokyo) ap-northeast-1' },
+  { value: 'ca-central-1', label: 'Canada (Central) ca-central-1' },
+  { value: 'eu-central-1', label: 'Europe (Frankfurt) eu-central-1' },
+  { value: 'eu-west-1', label: 'Europe (Ireland) eu-west-1' },
+  { value: 'eu-west-2', label: 'Europe (London) eu-west-2' },
+  { value: 'eu-south-1', label: 'Europe (Milan) eu-south-1' },
+  { value: 'eu-west-3', label: 'Europe (Paris) eu-west-3' },
+  { value: 'eu-north-1', label: 'Europe (Stockholm) eu-north-1' },
+  { value: 'me-south-1', label: 'Middle East (Bahrain) me-south-1' },
+  { value: 'sa-east-1', label: 'South America (São Paulo) sa-east-1' },
+];
+
+// TODO: Consolidate across forms w/ HOC
 export default class GCPFormSection extends Component<PropsType, StateType> {
   state = {
-    gcpRegion: '',
+    gcpRegion: 'us-east-1',
     gcpProjectId: '',
     gcpKeyData: '',
-    selectedInfras: [] as { value: string, label: string }[],
+    selectedInfras: [...provisionOptions],
+    buttonStatus: '',
+  }
+
+  componentDidMount = () => {
+    let { infras } = this.props;
+    let { selectedInfras } = this.state;
+
+    if (infras) {
+      
+      // From the dashboard, only uncheck and disable if "creating" or "created"
+      let filtered = selectedInfras;
+      infras.forEach(
+        (infra: InfraType, i: number) => {
+          let { kind, status } = infra;
+          if (status === 'creating' || status === 'created') {
+            filtered = filtered.filter((item: any) => {
+              return item.value !== kind;
+            });
+          }
+        }
+      );
+      this.setState({ selectedInfras: filtered });
+    }
+  }
+
+  checkFormDisabled = () => {
+    let { 
+      gcpRegion,
+      gcpProjectId, 
+      gcpKeyData, 
+      selectedInfras,
+    } = this.state;
+    let { projectName } = this.props;
+    if (projectName || projectName === '') {
+      return (
+        !isAlphanumeric(projectName) 
+          || !(gcpProjectId !== '' && gcpKeyData !== '' && gcpRegion !== '')
+          || selectedInfras.length === 0
+      );
+    } else {
+      return (
+        !(gcpProjectId !== '' && gcpKeyData !== '' && gcpRegion !== '')
+          || selectedInfras.length === 0
+      );
+    }
+  }
+
+  // Step 1: Create a project
+  createProject = (callback?: any) => {
+    console.log('Creating project');
+    let { projectName, handleError } = this.props;
+    let { 
+      user, 
+      setProjects, 
+      setCurrentProject, 
+    } = this.context;
+
+    api.createProject('<token>', { name: projectName }, {
+    }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+        handleError();
+        return;
+      } else {
+        let proj = res.data;
+
+        // Need to set project list for dropdown
+        // TODO: consolidate into ProjectSection (case on exists in list on set)
+        api.getProjects('<token>', {}, { 
+          id: user.userId 
+        }, (err: any, res: any) => {
+          if (err) {
+            console.log(err);
+            handleError();
+            return;
+          }
+          setProjects(res.data);
+          setCurrentProject(proj);
+          callback && callback();
+        });
+      }
+    });
+  }
+
+  provisionGCR = (id: number, callback?: any) => {
+    console.log('Provisioning GCR')
+    let { currentProject } = this.context;
+    let { handleError } = this.props;
+
+    api.createGCR('<token>', {
+      gcp_integration_id: id,
+    }, { project_id: currentProject.id }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+        handleError();
+        return;
+      }
+      callback && callback();
+    });
+  }
+
+  provisionGKE = (id: number) => {
+    console.log('Provisioning GKE');
+    let { setCurrentView, handleError } = this.props;
+    let { currentProject } = this.context;
+
+    let clusterName = `${currentProject.name}-cluster`
+    api.createGKE('<token>', {
+      gke_name: clusterName,
+      gcp_integration_id: id,
+    }, { project_id: currentProject.id }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+        handleError();
+        return;
+      }
+      setCurrentView('provisioner');
+    })
+  }
+
+  handleCreateFlow = () => {
+    let { setCurrentView } = this.props;
+    let { selectedInfras, gcpKeyData, gcpProjectId, gcpRegion } = this.state;
+    let { currentProject } = this.context;
+    api.createGCPIntegration('<token>', {
+      gcp_region: gcpRegion,
+      gcp_key_data: gcpKeyData,
+      gcp_project_id: gcpProjectId,
+    }, { project_id: currentProject.id }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+      } else if (res?.data) {
+        console.log('gcp provisioned with response: ', res.data);
+        let { id } = res.data;
+
+        if (selectedInfras.length === 2) {
+          // Case: project exists, provision GCR + GKE
+          this.provisionGCR(id, () => this.provisionGKE(id));
+        } else if (selectedInfras[0].value === 'gcr') {
+          // Case: project exists, only provision GCR
+          this.provisionGCR(id, () => setCurrentView('provisioner'));
+        } else {
+          // Case: project exists, only provision GKE
+          this.provisionGKE(id);
+        }
+      }
+    });
+  }
+
+  // TODO: handle generically (with > 2 steps)
+  onCreateGCP = () => {
+    let { projectName } = this.props;
+
+    if (!projectName) {
+      this.handleCreateFlow();
+    } else {
+      this.createProject(this.handleCreateFlow);
+    }
   }
 
   render() {
@@ -51,28 +243,27 @@ export default class GCPFormSection extends Component<PropsType, StateType> {
           <Heading isAtTop={true}>
             GCP Credentials
             <GuideButton 
-              href='https://docs.getporter.dev/docs/getting-started-with-porter-on-gcp' 
+              href='https://docs.getporter.dev/docs/getting-started-with-porter-on-aws' 
               target='_blank'
             >
               <i className="material-icons-outlined">help</i> 
               Guide
             </GuideButton>
           </Heading>
-          <InputRow
-            type='text'
+          <SelectRow
+            options={regionOptions}
+            width='100%'
             value={gcpRegion}
-            setValue={(x: string) => this.setState({ gcpRegion: x })}
+            dropdownMaxHeight='240px'
+            setActiveValue={(x: string) => this.setState({ gcpRegion: x })}
             label='📍 GCP Region'
-            placeholder='ex: us-central1-a'
-            width='100%'
-            isRequired={true}
           />
           <InputRow
             type='text'
             value={gcpProjectId}
             setValue={(x: string) => this.setState({ gcpProjectId: x })}
             label='🏷️ GCP Project ID'
-            placeholder='ex: pale-moon-24601'
+            placeholder='ex: AKIAIOSFODNN7EXAMPLE'
             width='100%'
             isRequired={true}
           />
@@ -86,20 +277,21 @@ export default class GCPFormSection extends Component<PropsType, StateType> {
             isRequired={true}
           />
           <Br />
-          <Heading>Resources</Heading>
-          <Helper>Porter will provision the following resources</Helper>
+          <Heading>GCP Resources</Heading>
+          <Helper>Porter will provision the following GCP resources</Helper>
           <CheckboxList
-            options={dummyOptions}
+            options={provisionOptions}
             selected={selectedInfras}
             setSelected={(x: { value: string, label: string }[]) => {
               this.setState({ selectedInfras: x });
             }}
           />
         </FormSection>
+        {this.props.children ? this.props.children : <Padding />}
         <SaveButton
           text='Submit'
-          disabled={true}
-          onClick={() => console.log('oolala')}
+          disabled={this.checkFormDisabled()}
+          onClick={this.onCreateGCP}
           makeFlush={true}
           helper='Note: Provisioning can take up to 15 minutes'
         />
@@ -108,6 +300,12 @@ export default class GCPFormSection extends Component<PropsType, StateType> {
   }
 }
 
+GCPFormSection.contextType = Context;
+
+const Padding = styled.div`
+  height: 15px;
+`;
+
 const Br = styled.div`
   width: 100%;
   height: 2px;
@@ -115,7 +313,7 @@ const Br = styled.div`
 
 const StyledGCPFormSection = styled.div`
   position: relative;
-  padding-bottom: 70px;
+  padding-bottom: 35px;
 `;
 
 const FormSection = styled.div`
@@ -123,6 +321,7 @@ const FormSection = styled.div`
   margin-top: 25px;
   background: #26282f;
   border-radius: 5px;
+  margin-bottom: 25px;
   padding: 25px;
   padding-bottom: 16px;
   font-size: 13px;

+ 7 - 1
dashboard/src/main/home/provisioner/ProvisionerSettings.tsx

@@ -95,10 +95,16 @@ export default class NewProject extends Component<PropsType, StateType> {
       case 'gcp':
         return (
           <GCPFormSection 
+            handleError={this.handleError}
+            projectName={projectName}
+            infras={infras}
+            setCurrentView={setCurrentView}
             setSelectedProvisioner={(x: string | null) => {
               this.setState({ selectedProvider: x });
             }}
-          />
+          >
+            {renderSkipHelper()}
+          </GCPFormSection>
         );
       case 'do':
         return (

+ 1 - 0
dashboard/src/main/home/provisioner/ProvisionerStatus.tsx

@@ -67,6 +67,7 @@ export default class ProvisionerStatus extends Component<PropsType, StateType> {
         console.log(err);
       } 
       let infras = filterOldInfras(res.data);
+      console.log('filtered infras: ', infras);
       let error = false;
 
       let maxStep = {} as Record<string, number>

+ 1 - 0
dashboard/src/shared/common.tsx

@@ -141,5 +141,6 @@ export const filterOldInfras = (infras: InfraType[]): InfraType[] => {
   let result = newestInfras.filter((x: InfraType) => {
     return whitelistedInfras.includes(x.kind)
   });
+  console.log('filtered infras (helper internal): ', result);
   return result;
 }