Ver Fonte

handle provisioner render

jusrhee há 5 anos atrás
pai
commit
da52d0c669

+ 16 - 6
dashboard/src/main/CurrentError.tsx

@@ -5,6 +5,7 @@ import close from '../assets/close.png';
 import { Context } from '../shared/Context';
 
 type PropsType = {
+  currentError: string,
 };
 
 type StateType = {
@@ -12,15 +13,24 @@ type StateType = {
 
 export default class CurrentError extends Component<PropsType, StateType> {
   state = {
-    expanded: false
+    expanded: false,
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if (
+      prevProps.currentError !== this.props.currentError
+      && this.props.currentError === 'Provisioning failed. Check your credentials and try again.'
+    ) {
+      this.setState({ expanded: true });
+    }
   }
   
   render() {
-    if (this.context.currentError) {
+    if (this.props.currentError) {
       if (!this.state.expanded) {
         return (
           <StyledCurrentError onClick={() => this.setState({ expanded: true })}>
-            <ErrorText>Error: {this.context.currentError}</ErrorText>
+            <ErrorText>Error: {this.props.currentError}</ErrorText>
             <CloseButton onClick={(e) => {
               this.context.setCurrentError(null);
               e.stopPropagation();
@@ -33,7 +43,7 @@ export default class CurrentError extends Component<PropsType, StateType> {
 
       return (
         <ExpandedError onClick={() => this.setState({ expanded: false })}>
-          Error: {this.context.currentError}
+          Error: {this.props.currentError}
           <CloseButtonAlt onClick={() => this.context.setCurrentError(null)}>
             <CloseButtonImg src={close} />
           </CloseButtonAlt>
@@ -80,9 +90,9 @@ const ErrorText = styled.div`
 
 const StyledCurrentError = styled.div`
   position: fixed;
-  bottom: 20px;
+  bottom: 22px;
   width: 300px;
-  left: 17px;
+  left: 100px;
   padding: 15px;
   padding-right: 0px;
   font-family: 'Work Sans', sans-serif;

+ 7 - 3
dashboard/src/main/Main.tsx

@@ -32,7 +32,6 @@ export default class Main extends Component<PropsType, StateType> {
   componentDidMount() {
     let { setUser } = this.context;
     api.checkAuth('', {}, {}, (err: any, res: any) => {    
-      console.log(err)  
       if (err && err.response?.status == 403) {
         this.setState({ isLoggedIn: false, loading: false })
       }
@@ -88,7 +87,12 @@ export default class Main extends Component<PropsType, StateType> {
 
         <Route path='/dashboard' render={() => {
           if (this.state.isLoggedIn && this.state.initialized) {
-            return <Home logOut={this.handleLogOut} />
+            return (
+              <Home 
+                currentProject={this.context.currentProject} 
+                logOut={this.handleLogOut} 
+              />
+            );
           } else {
             return <Redirect to='/' />
           }
@@ -114,7 +118,7 @@ export default class Main extends Component<PropsType, StateType> {
         <BrowserRouter>
           {this.renderMain()}
         </BrowserRouter>
-        <CurrentError />
+        <CurrentError currentError={this.context.currentError} />
       </StyledMain>
     );
   }

+ 35 - 46
dashboard/src/main/home/Home.tsx

@@ -1,10 +1,12 @@
 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';
-import { InfraType } from '../../shared/types';
+import { ProjectType } from '../../shared/types';
+import { includesCompletedInfraSet } from '../../shared/common';
 
 import Sidebar from './sidebar/Sidebar';
 import Dashboard from './dashboard/Dashboard';
@@ -19,12 +21,12 @@ import IntegrationsModal from './modals/IntegrationsModal';
 import IntegrationsInstructionsModal from './modals/IntegrationsInstructionsModal';
 import NewProject from './new-project/NewProject';
 import Navbar from './navbar/Navbar';
-import Provisioner from './new-project/Provisioner';
+import ProvisionerStatus from './provisioner/ProvisionerStatus';
 import ProjectSettings from './project-settings/ProjectSettings';
-import posthog from 'posthog-js';
 
 type PropsType = {
-  logOut: () => void
+  logOut: () => void,
+  currentProject: ProjectType,
 };
 
 type StateType = {
@@ -39,6 +41,7 @@ type StateType = {
   sidebarReady: boolean, // Fixes error where ~1/3 times reloading to provisioner fails
 };
 
+// TODO: Handle cluster connected but with some failed infras (no successful set)
 export default class Home extends Component<PropsType, StateType> {
   state = {
     forceSidebar: true,
@@ -50,43 +53,37 @@ export default class Home extends Component<PropsType, StateType> {
     sidebarReady: false,
   }
 
-  // Possibly consolidate into context (w/ ProjectSection + NewProject)
+  initializeView = () => {
+    let { currentCluster } = this.context;
+    let { currentProject } = this.props;
+    // Check if current project is provisioning
+    api.getInfra('<token>', {}, { project_id: currentProject.id }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+        return;
+      }
+      if (!currentCluster && !includesCompletedInfraSet(res.data)) {
+        this.setState({ currentView: 'provisioner', sidebarReady: true, });
+      } else {
+        this.setState({ currentView: 'dashboard', sidebarReady: true });
+      }
+    });
+  }
+
   getProjects = () => {
-    let { user, currentProject, projects, setProjects } = this.context;
+    let { user, setProjects } = this.context;
+    let { currentProject } = this.props;
     api.getProjects('<token>', {}, { id: user.userId }, (err: any, res: any) => {
       if (err) {
         console.log(err);
       } else if (res.data) {
-        setProjects(res.data);
-        if (res.data.length > 0 && !currentProject) {
+        if (res.data.length === 0) {
+          this.setState({ currentView: 'new-project', sidebarReady: true, });
+        } else if (res.data.length > 0 && !currentProject) {
+          setProjects(res.data);
           this.context.setCurrentProject(res.data[0]);
 
-          // Check if current project is provisioning
-          api.getInfra('<token>', {}, { project_id: res.data[0].id }, (err: any, res: any) => {
-            if (err) {
-              console.log(err);
-            } else if (res.data) {
-
-              let viewData = [] as any[]
-              // TODO: separately handle non meta-provisioning case
-              res.data.forEach((el: InfraType) => {
-                if (el.status === 'creating') {
-                  viewData.push({
-                    infra_id: el.id,
-                    kind: el.kind,
-                  });
-                }
-              });
-              
-              if (viewData.length > 0) {
-                this.setState({ currentView: 'provisioner', viewData, sidebarReady: true, });
-              } else {
-                this.setState({ sidebarReady: true });
-              }
-            }
-          });
-        } else if (res.data.length === 0) {
-          this.setState({ currentView: 'new-project', sidebarReady: true, });
+          this.initializeView();
         }
       }
     });
@@ -103,15 +100,8 @@ export default class Home extends Component<PropsType, StateType> {
   }
 
   componentDidUpdate(prevProps: PropsType) {
-    if (prevProps !== this.props && this.context.currentProject) {
-
-      // Set view to dashboard on project change
-      if (this.state.prevProjectId && this.state.prevProjectId !== this.context.currentProject.id) {
-        this.setState({
-          prevProjectId: this.context.currentProject.id,
-          currentView: 'dashboard'
-        });
-      }
+    if (prevProps.currentProject !== this.props.currentProject) {
+      this.initializeView();
     }
   }
 
@@ -169,9 +159,8 @@ export default class Home extends Component<PropsType, StateType> {
       );
     } else if (currentView === 'provisioner') {
       return (
-        <Provisioner 
+        <ProvisionerStatus
           setCurrentView={(x: string) => this.setState({ currentView: x })}
-          viewData={this.state.viewData}
         />
       );
     } else if (currentView === 'project-settings') {
@@ -201,7 +190,7 @@ export default class Home extends Component<PropsType, StateType> {
       // Force sidebar closed on first provision
       if (this.state.currentView === 'provisioner' && this.state.forceSidebar) {
         this.setState({ forceSidebar: false });
-      } else if (this.state.sidebarReady) {
+      } else {
         return (
           <Sidebar
             forceSidebar={this.state.forceSidebar}

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

@@ -4,6 +4,7 @@ import styled from 'styled-components';
 
 import gradient from '../../../assets/gradient.jpg';
 import { Context } from '../../../shared/Context';
+import { InfraType } from '../../../shared/types';
 import api from '../../../shared/api';
 
 import ProvisionerSettings from '../provisioner/ProvisionerSettings';
@@ -14,28 +15,33 @@ type PropsType = {
 };
 
 type StateType = {
+  infras: InfraType[],
 };
 
 export default class Dashboard extends Component<PropsType, StateType> {
+  state = {
+    infras: [] as InfraType[],
+  }
+
   refreshInfras = () => {
-    api.getInfra('<token>', {}, { 
-      project_id: this.props.projectId,
-    }, (err: any, res: any) => {
-      if (err) {
-        console.log(err);
-        return;
-      } 
-      console.log(res.data);
-    });
+    if (this.props.projectId) {
+      api.getInfra('<token>', {}, { 
+        project_id: this.props.projectId,
+      }, (err: any, res: any) => {
+        if (err) {
+          console.log(err);
+          return;
+        } 
+        this.setState({ infras: res.data });
+      });
+    }
   }
   
   componentDidMount() {
-    console.log('mounty')
     this.refreshInfras();
   }
 
   componentDidUpdate(prevProps: PropsType) {
-    console.log('washy')
     if (this.props.projectId && prevProps.projectId !== this.props.projectId) {
       this.refreshInfras();
     }
@@ -53,6 +59,7 @@ export default class Dashboard extends Component<PropsType, StateType> {
   render() {
     let { currentProject, currentCluster } = this.context;
     let { setCurrentView } = this.props;
+    let { infras } = this.state;
     let { onShowProjectSettings } = this;
     return (
       <>
@@ -95,6 +102,7 @@ export default class Dashboard extends Component<PropsType, StateType> {
             )}
             <ProvisionerSettings 
               setCurrentView={setCurrentView} 
+              infras={infras}
             />
           </DashboardWrapper>
         )}

+ 0 - 227
dashboard/src/main/home/dashboard/DashboardWrapper.tsx

@@ -1,227 +0,0 @@
-import { render } from '@testing-library/react';
-import React, { Component } from 'react';
-import styled from 'styled-components';
-
-import gradient from '../../../assets/gradient.jpg';
-import { Context } from '../../../shared/Context';
-import api from '../../../shared/api';
-
-import ProvisionerSettings from '../provisioner/ProvisionerSettings';
-
-type PropsType = {
-  setCurrentView: (x: string) => void,
-  projectId: number | null,
-};
-
-type StateType = {
-};
-
-export default class Dashboard extends Component<PropsType, StateType> {
-  componentDidUpdate(prevProps: PropsType) {
-    if (this.props.projectId && prevProps.projectId !== this.props.projectId) {
-      api.getInfra('<token>', {}, { 
-        project_id: this.props.projectId,
-      }, (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          return;
-        } 
-        console.log(res.data);
-      });
-    }
-  }
-
-  onShowProjectSettings = () => {
-    let { currentProject, setCurrentModal } = this.context;
-    let { setCurrentView } = this.props;
-    setCurrentModal('UpdateProjectModal', { 
-      currentProject: currentProject,
-      setCurrentView: setCurrentView,
-    });
-  }
-
-  render() {
-    let { currentProject, currentCluster } = this.context;
-    let { setCurrentView } = this.props;
-    let { onShowProjectSettings } = this;
-    return (
-      <>
-        {currentProject && (
-          <DashboardWrapper>
-            <TitleSection>
-            <DashboardIcon>
-              <DashboardImage src={gradient} />
-              <Overlay>
-                {currentProject && currentProject.name[0].toUpperCase()}
-              </Overlay>
-            </DashboardIcon>
-              <Title>{currentProject && currentProject.name}</Title>
-              <i
-                className="material-icons"
-                onClick={onShowProjectSettings}
-              >
-                more_vert
-              </i>
-            </TitleSection>
-
-            <InfoSection>
-              <TopRow>
-                <InfoLabel>
-                  <i className="material-icons">info</i> Info
-              </InfoLabel>
-              </TopRow>
-              <Description>
-                Project overview for {currentProject && currentProject.name}.
-              </Description>
-            </InfoSection>
-
-            <LineBreak />
-
-            {!currentCluster && (
-              <Banner>
-                <i className="material-icons">error_outline</i>
-                This project currently has no clusters connected.
-              </Banner>
-            )}
-            <ProvisionerSettings 
-              setCurrentView={setCurrentView} 
-            />
-          </DashboardWrapper>
-        )}
-      </>
-    );
-  }
-}
-
-Dashboard.contextType = Context;
-
-const DashboardWrapper = styled.div`
-  padding-bottom: 100px;
-`;
-
-const Banner = styled.div`
-  height: 40px;
-  width: 100%;
-  margin: 10px 0 30px;
-  font-size: 13px;
-  display: flex;
-  border-radius: 5px;
-  padding-left: 15px;
-  align-items: center;
-  background: #616FEEcc;
-  > i {
-    margin-right: 10px;
-    font-size: 18px;
-  }
-`;
-
-const TopRow = styled.div`
-  display: flex;
-  align-items: center;
-`;
-
-const Description = styled.div`
-  color: #ffffff;
-  margin-top: 13px;
-  margin-left: 2px;
-  font-size: 13px;
-`;
-
-const InfoLabel = styled.div`
-  width: 72px;
-  height: 20px;
-  display: flex;
-  align-items: center;
-  color: #7A838F;
-  font-size: 13px;
-  > i {
-    color: #8B949F;
-    font-size: 18px;
-    margin-right: 5px;
-  }
-`;
-
-const InfoSection = styled.div`
-  margin-top: 20px;
-  font-family: 'Work Sans', sans-serif;
-  margin-left: 0px;
-  margin-bottom: 35px;
-`;
-
-const LineBreak = styled.div`
-  width: calc(100% - 0px);
-  height: 2px;
-  background: #ffffff20;
-  margin: 10px 0px 35px;
-`;
-
-const Overlay = styled.div`
-  height: 100%;
-  width: 100%;
-  position: absolute;
-  background: #00000028;
-  top: 0;
-  left: 0;
-  border-radius: 5px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-size: 24px;
-  font-weight: 500;
-  font-family: 'Work Sans', sans-serif;
-  color: white;
-`;
-
-const DashboardImage = styled.img`
-  height: 45px;
-  width: 45px;
-  border-radius: 5px;
-`;
-
-const DashboardIcon = styled.div`
-  position: relative;
-  height: 45px;
-  width: 45px;
-  border-radius: 5px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-
-  > i {
-    font-size: 22px;
-  }
-`;
-
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: 'Work Sans', sans-serif;
-  margin-left: 18px;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  height: 80px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding-left: 0px;
-
-  > i {
-    margin-left: 10px;
-    cursor: pointer;
-    font-size 18px;
-    color: #858FAAaa;
-    padding: 5px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-    margin-bottom: -3px;
-  }
-`;

+ 34 - 1
dashboard/src/main/home/provisioner/AWSFormSection.tsx

@@ -5,7 +5,7 @@ import close from '../../../assets/close.png';
 import { isAlphanumeric } from '../../../shared/common';
 import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
-import { ProjectType } from '../../../shared/types';
+import { ProjectType, InfraType } from '../../../shared/types';
 
 import InputRow from '../../../components/values-form/InputRow';
 import Helper from '../../../components/values-form/Helper';
@@ -18,6 +18,7 @@ type PropsType = {
   handleError: () => void,
   projectName: string,
   setCurrentView: (x: string | null, data?: any) => void,
+  infras: InfraType[],
 };
 
 type StateType = {
@@ -43,6 +44,38 @@ export default class AWSFormSection extends Component<PropsType, StateType> {
     buttonStatus: '',
   }
 
+  componentDidMount = () => {
+    let { infras } = this.props;
+    let { selectedInfras } = this.state;
+
+    if (infras) {
+      
+      // From the dashboard, only uncheck if "creating" or "created"
+      let filtered = selectedInfras;
+      infras.forEach(
+        (infra: InfraType, i: number) => {
+          let { kind, status } = infra;
+          if (
+            kind === 'ecr'
+            && (status === 'creating' || status === 'created')
+          ) {
+            filtered = filtered.filter((item: any) => {
+              return item.value !== 'ecr';
+            });
+          } else if (
+            kind === 'eks'
+            && (status === 'creating' || status === 'created')
+          ) {
+            filtered = filtered.filter((item: any) => {
+              return item.value !== 'eks';
+            });
+          }
+        }
+      );
+      this.setState({ selectedInfras: filtered });
+    }
+  }
+
   checkFormDisabled = () => {
     let { 
       awsRegion,

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

@@ -3,6 +3,7 @@ import styled from 'styled-components';
 
 import { Context } from '../../../shared/Context';
 import { integrationList } from '../../../shared/common';
+import { InfraType } from '../../../shared/types';
 
 import Helper from '../../../components/values-form/Helper';
 import AWSFormSection from './AWSFormSection';
@@ -14,10 +15,12 @@ type PropsType = {
   setCurrentView: (x: string, data?: any) => void,
   isInNewProject?: boolean,
   projectName?: string,
+  infras?: InfraType[],
 };
 
 type StateType = {
   selectedProvider: string | null,
+  infras: InfraType[],
 };
 
 const providers = ['aws', 'gcp', 'do',];
@@ -25,18 +28,21 @@ const providers = ['aws', 'gcp', 'do',];
 export default class NewProject extends Component<PropsType, StateType> {
   state = {
     selectedProvider: null as string | null,
+    infras: [] as InfraType[],
   }
 
   // Handle any submission (pre-status) error
   handleError = () => {
     let { setCurrentView } = this.props;
+    let { setCurrentError } = this.context;
     setCurrentView('dashboard');
     this.setState({ selectedProvider: null });
+    setCurrentError('Provisioning failed. Check your credentials and try again.');
   }
 
   renderSelectedProvider = () => {
     let { selectedProvider } = this.state;
-    let { projectName, setCurrentView } = this.props;
+    let { projectName, setCurrentView, infras } = this.props;
 
     let renderSkipHelper = () => {
       return (
@@ -76,6 +82,7 @@ export default class NewProject extends Component<PropsType, StateType> {
           <AWSFormSection 
             handleError={this.handleError}
             projectName={projectName}
+            infras={infras}
             setCurrentView={setCurrentView}
             setSelectedProvisioner={(x: string | null) => {
               this.setState({ selectedProvider: x });

+ 13 - 4
dashboard/src/main/home/new-project/Provisioner.tsx → dashboard/src/main/home/provisioner/ProvisionerStatus.tsx

@@ -6,13 +6,11 @@ import { Context } from '../../../shared/Context';
 import ansiparse from '../../../shared/ansiparser'
 import loading from '../../../assets/loading.gif';
 import warning from '../../../assets/warning.png';
+import { InfraType } from '../../../shared/types';
 
 import Helper from '../../../components/values-form/Helper';
-import { eventNames } from 'process';
-import { inflateRaw, inflateRawSync } from 'zlib';
 
 type PropsType = {
-  viewData: any,
   setCurrentView: (x: string) => void,
 }
 
@@ -130,7 +128,18 @@ export default class Provisioner extends Component<PropsType, StateType> {
   componentDidMount() {
     let { currentProject } = this.context;
     let protocol = process.env.NODE_ENV == 'production' ? 'wss' : 'ws'
-    let viewData = this.props.viewData || []
+
+    // Check if current project is provisioning
+    api.getInfra('<token>', {}, { project_id: currentProject.id }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+      } else if (res.data) {
+
+        let viewData = [] as any[]
+        console.log('do stuff')
+      }
+    });
+    let viewData = [] as InfraType[];
 
     let websockets = viewData.map((infra: any) => {
       let ws = new WebSocket(`${protocol}://${process.env.API_SERVER}/api/projects/${currentProject.id}/provision/${infra.kind}/${infra.infra_id}/logs`)

+ 4 - 1
dashboard/src/main/home/sidebar/ClusterSection.tsx

@@ -71,7 +71,10 @@ export default class ClusterSection extends Component<PropsType, StateType> {
             } else {
               setCurrentCluster(clusters[0]);
             }
-          } else if (this.props.currentView !== 'provisioner') {
+          } else if (
+            this.props.currentView !== 'provisioner'
+            && this.props.currentView !== 'new-project'
+          ) {
             this.setState({ clusters: [] });
             setCurrentCluster(null);
             this.props.setCurrentView('dashboard');

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

@@ -2,7 +2,6 @@ import React, { Component } from 'react';
 import styled from 'styled-components';
 import gradient from '../../../assets/gradient.jpg';
 
-import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
 import { ProjectType, InfraType } from '../../../shared/types';
 
@@ -21,40 +20,15 @@ export default class ProjectSection extends Component<PropsType, StateType> {
     expanded: false,
   };
 
-  handleSelectProject = (project: ProjectType) => {
-    this.context.setCurrentProject(project);
-    
-    api.getInfra('<token>', {}, { project_id: project.id }, (err: any, res: any) => {
-      if (err) {
-        console.log(err);
-      } else if (res.data) {
-
-        let viewData = [] as any[]
-        res.data.forEach((el: InfraType) => {
-          if (el.status === 'creating') {
-            viewData.push({
-              infra_id: el.id,
-              kind: el.kind,
-            });
-          }
-        });
-
-        if (viewData.length > 0) {
-          this.props.setCurrentView('provisioner', viewData);
-        } else {
-          this.props.setCurrentView('dashboard');
-        }
-      }
-    });
-  }
-
   renderOptionList = () => {
+    let { setCurrentProject } = this.context;
+
     return this.props.projects.map((project: ProjectType, i: number) => {
       return (
         <Option
           key={i}
           selected={project.name === this.props.currentProject.name}
-          onClick={() => this.handleSelectProject(project)}
+          onClick={() => setCurrentProject(project)}
         >
           <ProjectIcon>
             <ProjectImage src={gradient} />

+ 29 - 33
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -94,44 +94,40 @@ export default class Sidebar extends Component<PropsType, StateType> {
   };
 
   renderProjectContents = () => {
-    if (this.props.currentView === 'provisioner') {
-      return (
-        <ProjectPlaceholder>
-          <img src={loading} /> Creating . . .
-        </ProjectPlaceholder>
-      )
-    } else if (this.context.currentProject) {
+    let { currentView, setCurrentView } = this.props;
+    let { currentProject, setCurrentModal } = this.context;
+    if (currentProject) {
       return (
         <>
           <SidebarLabel>Home</SidebarLabel>
           <NavButton
-            onClick={() => this.props.setCurrentView('dashboard')}
-            selected={this.props.currentView === 'dashboard'}
+            onClick={() => setCurrentView('dashboard')}
+            selected={currentView === 'dashboard' || currentView === 'provisioner'}
           >
-            <img src={category} />
+            <Img src={category} />
             Dashboard
           </NavButton>
           <NavButton
-            onClick={() => this.props.setCurrentView('templates')}
-            selected={this.props.currentView === 'templates'}
+            onClick={() => setCurrentView('templates')}
+            selected={currentView === 'templates'}
           >
-            <img src={filter} />
+            <Img src={filter} />
             Templates
           </NavButton>
           <NavButton
-            selected={this.props.currentView === 'integrations'}
+            selected={currentView === 'integrations'}
             onClick={() => {
-              this.context.setCurrentModal('IntegrationsInstructionsModal', {})
+              setCurrentModal('IntegrationsInstructionsModal', {})
             }}
           >
-            <img src={integrations} />
+            <Img src={integrations} />
             Integrations
           </NavButton>
           <NavButton
-            onClick={() => this.props.setCurrentView('project-settings')}
-            selected={this.props.currentView === 'project-settings'}
+            onClick={() => setCurrentView('project-settings')}
+            selected={currentView === 'project-settings'}
           >
-            <img src={settings} />
+            <Img enlarge={true} src={settings} />
             Settings
           </NavButton>
 
@@ -142,9 +138,9 @@ export default class Sidebar extends Component<PropsType, StateType> {
             forceCloseDrawer={this.state.forceCloseDrawer} 
             releaseDrawer={() => this.setState({ forceCloseDrawer: false })}
             setWelcome={this.props.setWelcome}
-            currentView={this.props.currentView}
-            setCurrentView={this.props.setCurrentView}
-            isSelected={this.props.currentView === 'cluster-dashboard'}
+            currentView={currentView}
+            setCurrentView={setCurrentView}
+            isSelected={currentView === 'cluster-dashboard'}
             forceRefreshClusters={this.props.forceRefreshClusters}
             setRefreshClusters={this.props.setRefreshClusters}
           />
@@ -163,7 +159,7 @@ export default class Sidebar extends Component<PropsType, StateType> {
   // SidebarBg is separate to cover retracted drawer
   render() {
     return (
-      <div>
+      <>
         {this.renderPullTab()}
         <StyledSidebar showSidebar={this.state.showSidebar}>
           <SidebarBg />
@@ -184,7 +180,7 @@ export default class Sidebar extends Component<PropsType, StateType> {
 
           {this.renderProjectContents()}
         </StyledSidebar>
-      </div>
+      </>
     );
   }
 }
@@ -240,16 +236,16 @@ const NavButton = styled.div`
     left: 19px;
     top: 8px;
   }
+`;
 
-  > img {
-    padding: 4px 4px;
-    height: 23px;
-    width: 23px;
-    border-radius: 3px;
-    position: absolute;
-    left: 20px;
-    top: 9px;
-  }
+const Img = styled.img<{ enlarge?: boolean }>`
+  padding: 4px 4px;
+  height: ${props => props.enlarge ? '27px' : '23px'};
+  width: ${props => props.enlarge ? '27px' : '23px'};
+  border-radius: 3px;
+  position: absolute;
+  left: ${props => props.enlarge ? '19px' : '20px'};
+  top: 9px;
 `;
 
 const BottomSection = styled.div`

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

@@ -50,7 +50,6 @@ class ContextProvider extends Component {
     },
     user: null as any,
     setUser: (userId: number, email: string) => {
-      console.log('test');
       this.setState({ user: { userId, email } });
     },
     devOpsMode: true,

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

@@ -1,6 +1,7 @@
 import aws from '../assets/aws.png';
 import digitalOcean from '../assets/do.png';
 import gcp from '../assets/gcp.png';
+import { InfraType } from '../shared/types';
 
 export const integrationList: any = {
   'kubernetes': {
@@ -68,4 +69,37 @@ export const getIgnoreCase = (object: any, key: string) => {
   return object[Object.keys(object)
     .find(k => k.toLowerCase() === key.toLowerCase())
   ];
+}
+
+export const includesCompletedInfraSet = (infras: InfraType[]): boolean => {
+  if (infras.length === 0) {
+    return true;
+  }
+  
+  let infraSets = [
+    ['ecr', 'eks'],
+    ['gcr', 'gke'],
+    ['docr', 'doks']
+  ];
+
+  let completed = [] as string[];
+  infras.forEach((infra: InfraType, i: number) => {
+    if (infra.status === 'created') {
+      completed.push(infra.kind);
+    }
+  });
+
+  completed.forEach((kind: string, i: number) => {
+    infraSets.forEach((infraSet: string[], i: number) => {
+      infraSet.includes(kind) && infraSet.splice(infraSet.indexOf(kind), 1);
+    });
+  });
+
+  let anyCompleted = false;
+  infraSets.forEach((infraSet: string[], i: number) => {
+    if (infraSet.length === 0) {
+      anyCompleted = true;
+    }
+  })
+  return anyCompleted;
 }

+ 1 - 1
dashboard/src/shared/types.tsx

@@ -145,7 +145,7 @@ export interface ImageType {
 
 export interface InfraType {
   id: number,
-  project_d: number,
+  project_id: number,
   kind: string,
   status: string,
 }