Browse Source

integrations frontend reorganized

jusrhee 5 years ago
parent
commit
4cc038d547

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

@@ -3,7 +3,7 @@ import styled from 'styled-components';
 import info from '../../assets/info.svg';
 
 import api from '../../shared/api';
-import { getRegistryIcon } from '../../shared/common';
+import { getIntegrationIcon } from '../../shared/common';
 import { Context } from '../../shared/Context';
 
 import Loading from '../Loading';
@@ -73,7 +73,7 @@ export default class ImageSelector extends Component<PropsType, StateType> {
     }
 
     return images.map((image: any, i: number) => {
-      let icon = getRegistryIcon(image.kind);
+      let icon = getIntegrationIcon(image.kind);
       return (
         <ImageItem
           key={i}

+ 14 - 0
dashboard/src/components/values-form/Heading.tsx

@@ -0,0 +1,14 @@
+import React from 'react';  
+import styled from 'styled-components';
+
+export default function Heading(props: { children: string }) {
+  return <StyledHeading>{props.children}</StyledHeading>;
+}
+
+const StyledHeading = styled.div`
+  color: white;
+  font-weight: 500;
+  font-size: 16px;
+  margin-top: 30px;
+  margin-bottom: 5px;
+`;

+ 14 - 0
dashboard/src/components/values-form/Helper.tsx

@@ -0,0 +1,14 @@
+import React from 'react';  
+import styled from 'styled-components';
+
+export default function Helper(props: { children: string }) {
+  return <StyledHelper>{props.children}</StyledHelper>;
+}
+
+const StyledHelper = styled.div`
+  color: #aaaabb;
+  line-height: 1.6em;
+  font-size: 13px;
+  margin-bottom: 15px;
+  margin-top: 20px;
+`;

+ 7 - 1
dashboard/src/components/values-form/InputRow.tsx

@@ -13,9 +13,14 @@ type PropsType = {
 };
 
 type StateType = {
+  readOnly: boolean
 };
 
 export default class InputRow extends Component<PropsType, StateType> {
+  state = {
+    readOnly: true
+  }
+
   handleChange = (e: ChangeEvent<HTMLInputElement>) => {
     if (this.props.type === 'number') {
       this.props.setValue(parseInt(e.target.value));
@@ -23,7 +28,7 @@ export default class InputRow extends Component<PropsType, StateType> {
       this.props.setValue(e.target.value);
     }
   }
-
+  
   render() {
     let { label, value, type, unit, placeholder, width } = this.props;
     return (
@@ -31,6 +36,7 @@ export default class InputRow extends Component<PropsType, StateType> {
         <Label>{label}</Label>
         <InputWrapper>
           <Input
+            readOnly={this.state.readOnly} onFocus={() => this.setState({ readOnly: false })}
             disabled={this.props.disabled}
             placeholder={placeholder}
             width={width}

+ 64 - 0
dashboard/src/components/values-form/TextArea.tsx

@@ -0,0 +1,64 @@
+import React, { ChangeEvent, Component } from 'react';
+import styled from 'styled-components';
+
+type PropsType = {
+  label?: string,
+  value: string,
+  setValue: (x: string) => void,
+  placeholder?: string
+  width?: string
+  disabled?: boolean
+};
+
+type StateType = {
+};
+
+export default class TextArea extends Component<PropsType, StateType> {
+  handleChange = (e: any) => {
+    this.props.setValue(e.target.value);
+  }
+
+  render() {
+    let { label, value, placeholder, width } = this.props;
+    return (
+      <StyledTextArea>
+        <Label>{label}</Label>
+        <InputArea
+          disabled={this.props.disabled}
+          placeholder={placeholder}
+          width={width}
+          value={value || ''}
+          onChange={this.handleChange}
+        />
+      </StyledTextArea>
+    );
+  }
+}
+
+const InputArea = styled.textarea`
+  outline: none;
+  border: none;
+  resize: none;
+  font-size: 13px;
+  background: #ffffff11;
+  border: 1px solid #ffffff55;
+  border-radius: 3px;
+  width: ${(props: { disabled: boolean, width: string }) => props.width ? props.width : '270px'};
+  color: ${(props: { disabled: boolean, width: string }) => props.disabled ? '#ffffff44' : 'white'};
+  padding: 5px 10px;
+  margin-right: 8px;
+  height: 8em;
+  line-height: 1.5em;
+`;
+
+const Label = styled.div`
+  color: #ffffff;
+  margin-bottom: 10px;
+  font-size: 13px;
+  font-family: 'Work Sans', sans-serif;
+`;
+
+const StyledTextArea = styled.div`
+  margin-bottom: 15px;
+  margin-top: 20px;
+`;

+ 2 - 16
dashboard/src/components/values-form/ValuesForm.tsx

@@ -9,6 +9,8 @@ import SaveButton from '../SaveButton';
 import CheckboxRow from './CheckboxRow';
 import InputRow from './InputRow';
 import SelectRow from './SelectRow';
+import Helper from './Helper';
+import Heading from './Heading';
 
 type PropsType = {
   onSubmit: (formValues: any) => void,
@@ -166,22 +168,6 @@ const Wrapper = styled.div`
   height: 100%;
 `;
 
-const Helper = styled.div`
-  color: #aaaabb;
-  line-height: 1.6em;
-  font-size: 13px;
-  margin-bottom: 15px;
-  margin-top: 20px;
-`;
-
-const Heading = styled.div`
-  color: white;
-  font-weight: 500;
-  font-size: 16px;
-  margin-top: 30px;
-  margin-bottom: 5px;
-`;
-
 const StyledValuesForm = styled.div`
   width: 100%;
   height: 100%;

+ 1 - 0
dashboard/src/main/home/Home.tsx

@@ -77,6 +77,7 @@ export default class Home extends Component<PropsType, StateType> {
         <ClusterDashboard
           currentCluster={currentCluster}
           setSidebar={(x: boolean) => this.setState({ forceSidebar: x })}
+          setCurrentView={(x: string) => this.setState({ currentView: x })}
         />
       </DashboardWrapper>
     );

+ 8 - 5
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -13,6 +13,7 @@ import ExpandedChart from './expanded-chart/ExpandedChart';
 type PropsType = {
   currentCluster: Cluster,
   setSidebar: (x: boolean) => void
+  setCurrentView: (x: string) => void,
 };
 
 type StateType = {
@@ -108,8 +109,10 @@ export default class ClusterDashboard extends Component<PropsType, StateType> {
         <LineBreak />
         
         <ControlRow>
-          <Button disabled={true}>
-            <i className="material-icons">add</i> Deploy a Chart
+          <Button
+            onClick={() => this.props.setCurrentView('templates')}
+          >
+            <i className="material-icons">add</i> Deploy Template
           </Button>
           <NamespaceSelector
             setNamespace={(namespace) => this.setState({ namespace })}
@@ -198,11 +201,11 @@ const Button = styled.div`
   white-space: nowrap;
   text-overflow: ellipsis;
   box-shadow: 0 5px 8px 0px #00000010;
-  cursor: not-allowed;
+  cursor: ${(props: { disabled?: boolean }) => props.disabled ? 'not-allowed' : 'pointer'};
 
-  background: ${(props: { disabled: boolean }) => props.disabled ? '#aaaabbee' :'#616FEEcc'};
+  background: ${(props: { disabled?: boolean }) => props.disabled ? '#aaaabbee' : '#616FEEcc'};
   :hover {
-    background: ${(props: { disabled: boolean }) => props.disabled ? '' : '#505edddd'};
+    background: ${(props: { disabled?: boolean }) => props.disabled ? '' : '#505edddd'};
   }
 
   > i {

File diff suppressed because it is too large
+ 0 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx


+ 24 - 36
dashboard/src/main/home/integrations/IntegrationList.tsx

@@ -2,55 +2,37 @@ import React, { Component } from 'react';
 import styled from 'styled-components';
 
 import { Context } from '../../../shared/Context';
-import { getRegistryIcon } from '../../../shared/common';
+import { getIntegrationIcon } from '../../../shared/common';
 import api from '../../../shared/api';
 
 type PropsType = {
-  setCurrentIntegration: (x: any) => void
+  setCurrent: (x: any) => void,
+  integrations: any,
+  isCategory?: boolean
 };
 
 type StateType = {
-  integrations: any[]
 };
 
-const dummyIntegrations = [
-  {
-    name: 'docker-hub',
-    label: 'Docker Hub',
-  },
-  {
-    name: 'gcr',
-    label: 'Google Container Registry (GCR)',
-  },
-  {
-    name: 'ecr',
-    label: 'Amazon Elastic Container Registry (ECR)',
-  },
-];
-
 export default class IntegrationList extends Component<PropsType, StateType> {
-  state = {
-    integrations: [] as any[]
-  }
-
-  componentDidMount() {
-    this.setState({ integrations: dummyIntegrations });
-  }
-
   renderContents = () => {
-    if (this.state.integrations) {
-      return this.state.integrations.map((integration: any, i: number) => {
-        let icon = getRegistryIcon(integration.name);
+    let { integrations, setCurrent, isCategory } = this.props;
+    if (integrations) {
+      return integrations.map((integration: any, i: number) => {
+        let icon = getIntegrationIcon(integration.value);
+        let disabled = integration.value === 'repo';
         return (
           <Integration
             key={i}
-            onClick={() => this.props.setCurrentIntegration(integration)}
+            onClick={() => disabled ? null : setCurrent(integration)}
+            isCategory={isCategory}
+            disabled={disabled}
           >
             <Flex>
               <Icon src={icon && icon} />
               <Label>{integration.label}</Label>
             </Flex>
-            <i className="material-icons">launch</i>
+            <i className="material-icons">{isCategory ? 'launch' : 'more_vert'}</i>
           </Integration>
         );
       });
@@ -85,19 +67,25 @@ const Integration = styled.div`
   align-items: center;
   justify-content: space-between;
   padding: 25px;
-  cursor: pointer;
   background: #26282f;
-  cursor: pointer;
+  cursor: ${(props: { isCategory: boolean, disabled: boolean }) => props.disabled ? 'not-allowed' : 'pointer'};
   margin-bottom: 15px;
   border-radius: 5px;
   box-shadow: 0 5px 8px 0px #00000033;
   :hover {
-    background: #ffffff11;
+    background: ${(props: { isCategory: boolean, disabled: boolean }) => props.disabled ? '' : '#ffffff11'};
+
+    > i {
+      background: ${(props: { isCategory: boolean, disabled: boolean }) => props.disabled ? '' : '#ffffff11'};
+    }
   }
 
   > i {
+    border-radius: 20px;
     font-size: 18px;
-    color: #616feecc;
+    padding: 5px;
+    color: ${(props: { isCategory: boolean, disabled: boolean }) => props.isCategory ? '#616feecc' : '#ffffff44'};
+    margin-right: -7px;
   }
 `;
 
@@ -109,7 +97,7 @@ const Label = styled.div`
 
 const Icon = styled.img`
   width: 30px;
-  margin-right: 15px;
+  margin-right: 18px;
 `;
 
 const Placeholder = styled.div`

+ 109 - 12
dashboard/src/main/home/integrations/Integrations.tsx

@@ -3,27 +3,90 @@ import styled from 'styled-components';
 
 import { Context } from '../../../shared/Context';
 import api from '../../../shared/api';
-import { getRegistryIcon } from '../../../shared/common';
+import { getIntegrationIcon } from '../../../shared/common';
+import { ChoiceType } from '../../../shared/types';
 
 import IntegrationList from './IntegrationList';
-import DockerHubForm from './integration-forms/DockerHubForm';
+import IntegrationForm from './integration-form/IntegrationForm';
 
 type PropsType = {
 };
 
 type StateType = {
-  currentIntegration: null | any
+  currentCategory: ChoiceType | null,
+  currentIntegration: any | null,
+  currentOptions: any[],
 };
 
+const categories = [
+  {
+    value: 'kubernetes',
+    label: 'Kubernetes',
+    buttonText: 'Add a Cluster',
+  },
+  {
+    value: 'registry',
+    label: 'Docker Registry',
+    buttonText: 'Add a Registry',
+  },
+  {
+    value: 'repo',
+    label: 'Git Repository',
+    buttonText: 'Add a Repository',
+  },
+];
+
 export default class Integrations extends Component<PropsType, StateType> {
   state = {
-    currentIntegration: null as null | any,
+    currentCategory: null as any | null,
+    currentIntegration: null as any | null,
+    currentOptions: [] as any[],
+  }
+
+  getIntegrations = (categoryType: string): any[] => {
+    switch (categoryType) {
+      case 'kubernetes':
+        return [
+          {
+            value: 'gke',
+            label: 'Google Kubernetes Engine (GKE)',
+          },
+          {
+            value: 'eks',
+            label: 'Amazon Elastic Kubernetes Service (EKS)',
+          },
+        ];
+      case 'registry':
+        return [
+          {
+            value: 'gcr',
+            label: 'Google Container Registry (GCR)',
+          },
+          {
+            value: 'ecr',
+            label: 'Elastic Container Registry (ECR)',
+          },
+          {
+            value: 'docker-hub',
+            label: 'Docker Hub',
+          },
+        ];
+      default:
+        return [];
+    }
+  }
+
+  componentDidUpdate(prevProps: PropsType, prevState: StateType) {
+    if (this.state.currentCategory && this.state.currentCategory !== prevState.currentCategory) {
+      this.setState({ currentOptions: this.getIntegrations(this.state.currentCategory.value) });
+    }
   }
 
   renderContents = () => {
-    let { currentIntegration } = this.state;
+    let { currentCategory, currentIntegration } = this.state;
+
     if (currentIntegration) {
-      let icon = getRegistryIcon(currentIntegration.name);
+      let icon = getIntegrationIcon(currentIntegration.value);
       return (
         <div>
           <TitleSectionAlt>
@@ -36,7 +99,38 @@ export default class Integrations extends Component<PropsType, StateType> {
             </Flex>
           </TitleSectionAlt>
 
-          <DockerHubForm />
+          <IntegrationForm integrationName={currentIntegration.value} />
+          <Br />
+        </div>
+      );
+    } else if (currentCategory) {
+      let icon = getIntegrationIcon(currentCategory.value);
+      return (
+        <div>
+          <TitleSectionAlt>
+            <Flex>
+              <i className="material-icons" onClick={() => this.setState({ currentCategory: null })}>
+                keyboard_backspace
+              </i>
+              <Icon src={icon && icon} />
+              <Title>{currentCategory.label}</Title>
+            </Flex>
+
+            <Button 
+              onClick={() => this.context.setCurrentModal('IntegrationsModal', { 
+                integrations: this.state.currentOptions,
+                setCurrentIntegration: (x: any) => this.setState({ currentIntegration: x })
+              })}
+            >
+              <i className="material-icons">add</i>
+              {currentCategory.buttonText}
+            </Button>
+          </TitleSectionAlt>
+
+          <IntegrationList
+            integrations={this.state.currentOptions}
+            setCurrent={(x: any) => this.setState({ currentIntegration: x })}
+          />
         </div>
       );
     }
@@ -44,14 +138,12 @@ export default class Integrations extends Component<PropsType, StateType> {
       <div>
         <TitleSection>
           <Title>Integrations</Title>
-          <Button onClick={() => this.context.setCurrentModal('IntegrationsModal', {})}>
-            <i className="material-icons">add</i>
-            Add Integration
-          </Button>
         </TitleSection>
 
         <IntegrationList
-          setCurrentIntegration={(x: any) => this.setState({ currentIntegration: x })}
+          integrations={categories}
+          setCurrent={(x: any) => this.setState({ currentCategory: x })}
+          isCategory={true}
         />
       </div>
     );
@@ -68,6 +160,11 @@ export default class Integrations extends Component<PropsType, StateType> {
 
 Integrations.contextType = Context;
 
+const Br = styled.div`
+  width: 100%;
+  height: 150px;
+`;
+
 const Icon = styled.img`
   width: 27px;
   margin-right: 12px;

+ 4 - 4
dashboard/src/main/home/integrations/integration-forms/DockerHubForm.tsx → dashboard/src/main/home/integrations/integration-form/DockerHubForm.tsx

@@ -27,7 +27,7 @@ export default class DockerHubForm extends Component<PropsType, StateType> {
 
   render() {
     return ( 
-      <StyledDockerHubForm>
+      <StyledForm>
         <CredentialWrapper>
           <InputRow
             type='text'
@@ -63,11 +63,11 @@ export default class DockerHubForm extends Component<PropsType, StateType> {
           />
         </CredentialWrapper>
         <SaveButton
-          text='Save Changes'
+          text='Save Settings'
           makeFlush={true}
           onClick={() => console.log('unimplemented')}
         />
-      </StyledDockerHubForm>
+      </StyledForm>
     );
   }
 }
@@ -78,7 +78,7 @@ const CredentialWrapper = styled.div`
   border-radius: 5px;
 `;
 
-const StyledDockerHubForm = styled.div`
+const StyledForm = styled.div`
   position: relative;
   padding-bottom: 75px;
 `;

+ 49 - 0
dashboard/src/main/home/integrations/integration-form/ECRForm.tsx

@@ -0,0 +1,49 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import { Context } from '../../../../shared/Context';
+import api from '../../../../shared/api';
+
+import InputRow from '../../../../components/values-form/InputRow';
+import TextArea from '../../../../components/values-form/TextArea';
+import SaveButton from '../../../../components/SaveButton';
+import Heading from '../../../../components/values-form/Heading';
+import Helper from '../../../../components/values-form/Helper';
+
+type PropsType = {
+};
+
+type StateType = {
+};
+
+export default class ECRForm extends Component<PropsType, StateType> {
+  state = {
+  }
+
+  render() {
+    return ( 
+      <StyledForm>
+        <CredentialWrapper>
+          <Heading>Coming Soon</Heading>
+          <Helper>Under construction.</Helper>
+        </CredentialWrapper>
+        <SaveButton
+          text='Save Settings'
+          makeFlush={true}
+          onClick={() => console.log('unimplemented')}
+        />
+      </StyledForm>
+    );
+  }
+}
+
+const CredentialWrapper = styled.div`
+  padding: 10px 40px 25px;
+  background: #ffffff11;
+  border-radius: 5px;
+`;
+
+const StyledForm = styled.div`
+  position: relative;
+  padding-bottom: 75px;
+`;

+ 101 - 0
dashboard/src/main/home/integrations/integration-form/EKSForm.tsx

@@ -0,0 +1,101 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import { Context } from '../../../../shared/Context';
+import api from '../../../../shared/api';
+
+import InputRow from '../../../../components/values-form/InputRow';
+import TextArea from '../../../../components/values-form/TextArea';
+import SaveButton from '../../../../components/SaveButton';
+import Heading from '../../../../components/values-form/Heading';
+import Helper from '../../../../components/values-form/Helper';
+
+type PropsType = {
+};
+
+type StateType = {
+  clusterName: string,
+  clusterEndpoint: string,
+  clusterCA: string,
+  awsAccessId: string,
+  awsSecretKey: string,
+};
+
+export default class EKSForm extends Component<PropsType, StateType> {
+  state = {
+    clusterName: '',
+    clusterEndpoint: '',
+    clusterCA: '',
+    awsAccessId: '',
+    awsSecretKey: '',
+  }
+
+  render() {
+    return ( 
+      <StyledForm>
+        <CredentialWrapper>
+          <Heading>Cluster Settings</Heading>
+          <Helper>Credentials for accessing your GKE cluster.</Helper>
+          <InputRow
+            type='text'
+            value={this.state.clusterName}
+            setValue={(x: string) => this.setState({ clusterName: x })}
+            label='🏷️ Cluster Name'
+            placeholder='ex: briny-pagelet'
+            width='100%'
+          />
+          <InputRow
+            type='text'
+            value={this.state.clusterEndpoint}
+            setValue={(x: string) => this.setState({ clusterEndpoint: x })}
+            label='🌐 Cluster Endpoint'
+            placeholder='ex: 00.00.000.00'
+            width='100%'
+          />
+          <TextArea
+            value={this.state.clusterCA}
+            setValue={(x: string) => this.setState({ clusterCA: x })}
+            label='🔏 Cluster Certificate'
+            placeholder='(Paste your certificate here)'
+            width='100%'
+          />
+
+          <Heading>AWS Settings</Heading>
+          <Helper>Service account credentials for GCP permissions.</Helper>
+          <InputRow
+            type='text'
+            value={this.state.awsAccessId}
+            setValue={(x: string) => this.setState({ awsAccessId: x })}
+            label='👤 AWS Access ID'
+            placeholder='ex: AKIAIOSFODNN7EXAMPLE'
+            width='100%'
+          />
+          <InputRow
+            type='password'
+            value={this.state.awsSecretKey}
+            setValue={(x: string) => this.setState({ awsSecretKey: x })}
+            label='🔒 AWS Secret Key'
+            placeholder='○ ○ ○ ○ ○ ○ ○ ○ ○'
+            width='100%'
+          />
+        </CredentialWrapper>
+        <SaveButton
+          text='Save Settings'
+          makeFlush={true}
+          onClick={() => console.log('unimplemented')}
+        />
+      </StyledForm>
+    );
+  }
+}
+
+const CredentialWrapper = styled.div`
+  padding: 10px 40px 25px;
+  background: #ffffff11;
+  border-radius: 5px;
+`;
+
+const StyledForm = styled.div`
+  position: relative;
+  padding-bottom: 75px;
+`;

+ 49 - 0
dashboard/src/main/home/integrations/integration-form/GCRForm.tsx

@@ -0,0 +1,49 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import { Context } from '../../../../shared/Context';
+import api from '../../../../shared/api';
+
+import InputRow from '../../../../components/values-form/InputRow';
+import TextArea from '../../../../components/values-form/TextArea';
+import SaveButton from '../../../../components/SaveButton';
+import Heading from '../../../../components/values-form/Heading';
+import Helper from '../../../../components/values-form/Helper';
+
+type PropsType = {
+};
+
+type StateType = {
+};
+
+export default class GCRForm extends Component<PropsType, StateType> {
+  state = {
+  }
+
+  render() {
+    return ( 
+      <StyledForm>
+        <CredentialWrapper>
+          <Heading>Coming Soon</Heading>
+          <Helper>Under construction.</Helper>
+        </CredentialWrapper>
+        <SaveButton
+          text='Save Settings'
+          makeFlush={true}
+          onClick={() => console.log('unimplemented')}
+        />
+      </StyledForm>
+    );
+  }
+}
+
+const CredentialWrapper = styled.div`
+  padding: 10px 40px 25px;
+  background: #ffffff11;
+  border-radius: 5px;
+`;
+
+const StyledForm = styled.div`
+  position: relative;
+  padding-bottom: 75px;
+`;

+ 90 - 0
dashboard/src/main/home/integrations/integration-form/GKEForm.tsx

@@ -0,0 +1,90 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import { Context } from '../../../../shared/Context';
+import api from '../../../../shared/api';
+
+import InputRow from '../../../../components/values-form/InputRow';
+import TextArea from '../../../../components/values-form/TextArea';
+import SaveButton from '../../../../components/SaveButton';
+import Heading from '../../../../components/values-form/Heading';
+import Helper from '../../../../components/values-form/Helper';
+
+type PropsType = {
+};
+
+type StateType = {
+  clusterName: string,
+  clusterEndpoint: string,
+  clusterCA: string,
+  serviceAccountKey: string
+};
+
+export default class GKEForm extends Component<PropsType, StateType> {
+  state = {
+    clusterName: '',
+    clusterEndpoint: '',
+    clusterCA: '',
+    serviceAccountKey: ''
+  }
+
+  render() {
+    return ( 
+      <StyledForm>
+        <CredentialWrapper>
+          <Heading>Cluster Settings</Heading>
+          <Helper>Credentials for accessing your GKE cluster.</Helper>
+          <InputRow
+            type='text'
+            value={this.state.clusterName}
+            setValue={(x: string) => this.setState({ clusterName: x })}
+            label='🏷️ Cluster Name'
+            placeholder='ex: briny-pagelet'
+            width='100%'
+          />
+          <InputRow
+            type='text'
+            value={this.state.clusterEndpoint}
+            setValue={(x: string) => this.setState({ clusterEndpoint: x })}
+            label='🌐 Cluster Endpoint'
+            placeholder='ex: 00.00.000.00'
+            width='100%'
+          />
+          <TextArea
+            value={this.state.clusterCA}
+            setValue={(x: string) => this.setState({ clusterCA: x })}
+            label='🔏 Cluster Certificate'
+            placeholder='(Paste your certificate here)'
+            width='100%'
+          />
+
+          <Heading>GCP Settings</Heading>
+          <Helper>Service account credentials for GCP permissions.</Helper>
+          <TextArea
+            value={this.state.serviceAccountKey}
+            setValue={(x: string) => this.setState({ serviceAccountKey: x })}
+            label='🔑 Service Account Key (JSON)'
+            placeholder='(Paste your JSON service account key here)'
+            width='100%'
+          />
+        </CredentialWrapper>
+        <SaveButton
+          text='Save Settings'
+          makeFlush={true}
+          onClick={() => console.log('unimplemented')}
+        />
+      </StyledForm>
+    );
+  }
+}
+
+const CredentialWrapper = styled.div`
+  padding: 10px 40px 25px;
+  background: #ffffff11;
+  border-radius: 5px;
+`;
+
+const StyledForm = styled.div`
+  position: relative;
+  padding-bottom: 75px;
+`;

+ 37 - 0
dashboard/src/main/home/integrations/integration-form/IntegrationForm.tsx

@@ -0,0 +1,37 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import DockerHubForm from './DockerHubForm';
+import GKEForm from './GKEForm';
+import EKSForm from './EKSForm';
+import GCRForm from './GCRForm';
+import ECRForm from './ECRForm';
+
+type PropsType = {
+  integrationName: string
+};
+
+type StateType = {
+};
+
+export default class IntegrationForm extends Component<PropsType, StateType> {
+  state = {
+  }
+
+  render() {
+    switch (this.props.integrationName) {
+      case 'docker-hub':
+        return <DockerHubForm />;
+      case 'gke':
+        return <GKEForm />;
+      case 'eks':
+        return <EKSForm />;
+      case 'ecr':
+        return <ECRForm />;
+      case 'gcr':
+        return <GCRForm />;
+      default:
+        return null;
+    }
+  }
+}

+ 21 - 32
dashboard/src/main/home/modals/IntegrationsModal.tsx

@@ -3,50 +3,38 @@ import styled from 'styled-components';
 import close from '../../../assets/close.png';
 
 import { Context } from '../../../shared/Context';
-import { getRegistryIcon } from '../../../shared/common';
+import { getIntegrationIcon } from '../../../shared/common';
 
 type PropsType = {
 };
 
 type StateType = {
-  integrations: any[]
 };
 
-const dummyIntegrations = [
-  {
-    name: 'docker-hub',
-    label: 'Docker Hub',
-  },
-  {
-    name: 'gcr',
-    label: 'Google Container Registry (GCR)',
-  },
-  {
-    name: 'ecr',
-    label: 'Amazon Elastic Container Registry (ECR)',
-  },
-];
-
 export default class IntegrationsModal extends Component<PropsType, StateType> {
   state = {
-    currentTab: 'mac',
-    integrations: [] as any[]
-  }
-
-  componentDidMount() {
-    this.setState({ integrations: dummyIntegrations });
   }
 
   renderIntegrationsCatalog = () => {
-    return this.state.integrations.map((integration: any, i: number) => {
-      let icon = getRegistryIcon(integration.name);
-      return (
-        <IntegrationOption key={i}>
-          <Icon src={icon && icon} />
-          <Label>{integration.label}</Label>
-        </IntegrationOption>
-      );
-    });
+    if (this.context.currentModalData) {
+      let { integrations, setCurrentIntegration } = this.context.currentModalData;
+      
+      return integrations.map((integration: any, i: number) => {
+        let icon = getIntegrationIcon(integration.value);
+        return (
+          <IntegrationOption 
+            key={i}
+            onClick={() => {
+              setCurrentIntegration(integration);
+              this.context.setCurrentModal(null, null);
+            }}
+          >
+            <Icon src={icon && icon} />
+            <Label>{integration.label}</Label>
+          </IntegrationOption>
+        );
+      });
+    }
   }
  
   render() {
@@ -84,6 +72,7 @@ const Icon = styled.img`
 
 const IntegrationOption = styled.div`
   height: 60px;
+  user-select: none;
   width: 100%;
   border-bottom: 1px solid #ffffff44;
   display: flex;

+ 12 - 3
dashboard/src/main/home/templates/expanded-template/LaunchTemplate.tsx

@@ -55,8 +55,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
     });
   }
 
-  componentDidMount() {
-
+  refreshTabs = () => {
     // Generate settings tabs from the provided form
     let tabOptions = [] as ChoiceType[];
     let tabContents = [] as any;
@@ -68,13 +67,17 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
             <ValuesForm 
               sections={tab.sections} 
               onSubmit={this.onSubmit}
-              disabled={this.state.selectedImageUrl === ''}
+              disabled={!this.state.selectedImageUrl || this.state.selectedImageUrl === ''}
             />
           </ValuesFormWrapper>
         ),
       });
     });
     this.setState({ tabOptions, tabContents });
+  }
+
+  componentDidMount() {
+    this.refreshTabs();
 
     // TODO: query with selected filter once implemented
     let { currentProject } = this.context;
@@ -90,6 +93,12 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
     });
   }
 
+  componentDidUpdate(prevProps: PropsType, prevState: StateType) {
+    if (this.state.selectedImageUrl !== prevState.selectedImageUrl) {
+      this.refreshTabs();
+    }
+  }
+
   renderIcon = (icon: string) => {
     if (icon) {
       return <Icon src={icon} />

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

@@ -1,11 +1,23 @@
-export const getRegistryIcon = (kind: string) => {
+export const getIntegrationIcon = (kind: string) => {
   switch (kind) {
+    case 'gke':
+      return 'https://sysdig.com/wp-content/uploads/2016/08/GKE_color.png';
+    case 'eks':
+      return 'https://img.stackshare.io/service/7991/amazon-eks.png';
+    case 'kubeconfig':
+      return 'https://uxwing.com/wp-content/themes/uxwing/download/10-brands-and-social-media/kubernetes.png';
     case 'docker-hub':
       return 'https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png';
     case 'gcr':
       return 'https://carlossanchez.files.wordpress.com/2019/06/21046548.png?w=640';
     case 'ecr':
       return 'https://avatars2.githubusercontent.com/u/52505464?s=400&u=da920f994c67665c7ad6c606a5286557d4f8555f&v=4';
+    case 'kubernetes':
+      return 'https://uxwing.com/wp-content/themes/uxwing/download/10-brands-and-social-media/kubernetes.png';
+    case 'repo':
+      return 'https://3.bp.blogspot.com/-xhNpNJJyQhk/XIe4GY78RQI/AAAAAAAAItc/ouueFUj2Hqo5dntmnKqEaBJR4KQ4Q2K3ACK4BGAYYCw/s1600/logo%2Bgit%2Bicon.png';
+    case 'registry':
+      return 'https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png';
     default:
       return null
   }

Some files were not shown because too many files changed in this diff