Răsfoiți Sursa

Overhauled Github Actions deployment + Added Github Actions to template deployment

Sean Rhee 5 ani în urmă
părinte
comite
71095cd313

+ 203 - 0
dashboard/src/components/repo-selector/ActionConfEditor.tsx

@@ -0,0 +1,203 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import github from '../../assets/github.png';
+import info from '../../assets/info.svg';
+
+import api from '../../shared/api';
+import { RepoType, ActionConfigType } from '../../shared/types';
+import { Context } from '../../shared/Context';
+
+import Loading from '../Loading';
+import BranchList from './BranchList';
+import ContentsList from './ContentsList';
+import ActionDetails from './ActionDetails';
+
+type PropsType = {
+  actionConfig: ActionConfigType | null,
+  branch: string,
+  pathIsSet: boolean,
+  setActionConfig: (x: ActionConfigType) => void,
+  setBranch: (x: string) => void,
+  setPath: (x: boolean) => void,
+};
+
+type StateType = {
+  repos: RepoType[],
+  loading: boolean,
+  error: boolean,
+};
+
+export default class ActionConfEditor extends Component<PropsType, StateType> {
+  state = {
+    repos: [] as RepoType[],
+    loading: true,
+    error: false,
+  }
+
+  componentDidMount() {
+    let { currentProject } = this.context;
+
+    // Get repos
+    api.getGitRepos('<token>', {
+    }, { project_id: currentProject.id }, (err: any, res: any) => {
+      if (err) {
+        this.setState({ loading: false, error: true });
+      } else {
+        var allRepos: any = [];
+        for (let i = 0; i < res.data.length; i++) {
+          var grid = res.data[i].id;
+          api.getGitRepoList('<token>', {}, { project_id: currentProject.id, git_repo_id: grid }, (err: any, res: any) => {
+            if (err) {
+              console.log(err);
+              this.setState({ loading: false, error: true });
+            } else {
+              res.data.forEach((repo: any, id: number) => {
+                repo.GHRepoID = grid;
+              })
+              allRepos = allRepos.concat(res.data);
+              this.setState({ repos: allRepos, loading: false, error: false });
+            }
+          })
+        }
+      }
+    });
+  }
+
+  setRepo = (x: RepoType) => {
+    let { actionConfig, setActionConfig } = this.props;
+    let updatedConfig = actionConfig;
+    updatedConfig.git_repo = x.FullName;
+    updatedConfig.git_repo_id = x.GHRepoID;
+    setActionConfig(updatedConfig);
+  }
+
+  renderRepoList = () => {
+    let { repos, loading, error } = this.state;
+    if (loading) {
+      return <LoadingWrapper><Loading /></LoadingWrapper>
+    } else if (error || !repos) {
+      return <LoadingWrapper>Error loading repos.</LoadingWrapper>
+    } else if (repos.length == 0) {
+      return <LoadingWrapper>No connected repos found.</LoadingWrapper>
+    }
+
+    return repos.map((repo: RepoType, i: number) => {
+      return (
+        <RepoName
+          key={i}
+          isSelected={repo.FullName === this.props.actionConfig.git_repo}
+          lastItem={i === repos.length - 1}
+          onClick={() => this.setRepo(repo)}
+        >
+          <img src={github} />{repo.FullName}
+        </RepoName>
+      );
+    });
+  }
+
+  renderExpanded = () => {
+    let {
+      actionConfig,
+      branch,
+      pathIsSet,
+      setActionConfig,
+      setBranch,
+      setPath,
+    } = this.props;
+
+    if (!actionConfig.git_repo) {
+      return (
+        <ExpandedWrapper>
+          {this.renderRepoList()}
+        </ExpandedWrapper>
+      );
+    } else if (!branch) {
+      return (
+        <ExpandedWrapperAlt>
+          <BranchList
+            actionConfig={actionConfig}
+            setBranch={(branch: string) => setBranch(branch)}
+          />
+        </ExpandedWrapperAlt>
+      );
+    } else if (!pathIsSet) {
+      return (
+        <ExpandedWrapperAlt>
+          <ContentsList
+            actionConfig={actionConfig}
+            branch={branch}
+            setActionConfig={setActionConfig}
+            setPath={() => setPath(true)}
+          />
+        </ExpandedWrapperAlt>
+      );
+    }
+    return (
+      <ExpandedWrapperAlt>
+        <ActionDetails
+          actionConfig={actionConfig}
+          setActionConfig={setActionConfig}
+        />
+      </ExpandedWrapperAlt>
+    )
+  }
+
+  render() {
+    return (
+      <>
+        {this.renderExpanded()}
+      </>
+    );
+  }
+}
+
+ActionConfEditor.contextType = Context;
+
+const RepoName = styled.div`
+  display: flex;
+  width: 100%;
+  font-size: 13px;
+  border-bottom: 1px solid ${(props: { lastItem: boolean, isSelected: boolean }) => props.lastItem ? '#00000000' : '#606166'};
+  color: #ffffff;
+  user-select: none;
+  align-items: center;
+  padding: 10px 0px;
+  cursor: pointer;
+  background: ${(props: { isSelected: boolean, lastItem: boolean }) => props.isSelected ? '#ffffff22' : '#ffffff11'};
+  :hover {
+    background: #ffffff22;
+
+    > i {
+      background: #ffffff22;
+    }
+  }
+
+  > img {
+    width: 18px;
+    height: 18px;
+    margin-left: 12px;
+    margin-right: 12px;
+  }
+`;
+
+const LoadingWrapper = styled.div`
+  padding: 30px 0px;
+  background: #ffffff11;
+  display: flex;
+  align-items: center;
+  font-size: 13px;
+  justify-content: center;
+  color: #ffffff44;
+`;
+
+const ExpandedWrapper = styled.div`
+  margin-top: 10px;
+  width: 100%;
+  border-radius: 3px;
+  border: 1px solid #ffffff44;
+  max-height: 275px;
+  overflow-y: auto;
+`;
+
+const ExpandedWrapperAlt = styled(ExpandedWrapper)`
+`;

+ 91 - 0
dashboard/src/components/repo-selector/ActionDetails.tsx

@@ -0,0 +1,91 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import { Context } from '../../shared/Context';
+import { ActionConfigType } from '../../shared/types';
+import InputRow from '../values-form/InputRow';
+
+type PropsType = {
+  actionConfig: ActionConfigType | null,
+  setActionConfig: (x: ActionConfigType) => void,
+};
+
+type StateType = {
+  dockerRepo: string,
+  error: boolean,
+};
+
+export default class ActionDetails extends Component<PropsType, StateType> {
+  state = {
+    dockerRepo: '',
+    error: false,
+  }
+
+  componentDidMount() {
+    if (this.props.actionConfig.dockerfile_path) {
+      this.setPath('/Dockerfile');
+    } else {
+      this.setPath('Dockerfile');
+    }
+  }
+
+  setPath = (x: string) => {
+    let { actionConfig, setActionConfig } = this.props;
+    let updatedConfig = actionConfig;
+    updatedConfig.dockerfile_path = updatedConfig.dockerfile_path.concat(x);
+    setActionConfig(updatedConfig);
+  }
+
+  setURL = (x: string) => {
+    let { actionConfig, setActionConfig } = this.props;
+    let updatedConfig = actionConfig;
+    updatedConfig.image_repo_uri = x;
+    setActionConfig(updatedConfig);
+  }
+
+  renderConfirmation = () => {
+    let { actionConfig } = this.props;
+    return (
+      <Holder>
+        <InputRow
+          disabled={true}
+          label='Git Repository'
+          type='text'
+          width='100%'
+          value={actionConfig.git_repo}
+          setValue={(x: string) => console.log(x)}
+        />
+        <InputRow
+          disabled={true}
+          label='Dockerfile Path'
+          type='text'
+          width='100%'
+          value={actionConfig.dockerfile_path}
+          setValue={(x: string) => console.log(x)}
+        />
+        <InputRow
+          label='Docker Image Repository'
+          placeholder='Image Repo URL (ex. gcr.io/porter/mr-p)'
+          type='text'
+          width='100%'
+          value={actionConfig.image_repo_uri}
+          setValue={(x: string) => this.setURL(x)}
+        />
+      </Holder>
+    )
+  }
+
+  render() {
+    return (
+      <div>
+        {this.renderConfirmation()}
+      </div>
+    );
+  }
+}
+
+ActionDetails.contextType = Context;
+
+const Holder = styled.div`
+  padding: 0px 12px;
+`;

+ 10 - 12
dashboard/src/components/repo-selector/BranchList.tsx

@@ -4,15 +4,13 @@ import branch_icon from '../../assets/branch.png';
 
 
 import api from '../../shared/api';
 import api from '../../shared/api';
 import { Context } from '../../shared/Context';
 import { Context } from '../../shared/Context';
+import { ActionConfigType } from '../..//shared/types';
 
 
 import Loading from '../Loading';
 import Loading from '../Loading';
 
 
 type PropsType = {
 type PropsType = {
-  grid: number,
-  repoName: string,
-  owner: string,
-  setSelectedBranch: (x: string) => void,
-  selectedBranch: string
+  actionConfig: ActionConfigType,
+  setBranch: (x: string) => void,
 };
 };
 
 
 type StateType = {
 type StateType = {
@@ -29,15 +27,16 @@ export default class BranchList extends Component<PropsType, StateType> {
   }
   }
 
 
   componentDidMount() {
   componentDidMount() {
+    let { actionConfig } = this.props;
     let { currentProject } = this.context;
     let { currentProject } = this.context;
 
 
     // Get branches
     // Get branches
     api.getBranches('<token>', {}, {
     api.getBranches('<token>', {}, {
       project_id: currentProject.id,
       project_id: currentProject.id,
-      git_repo_id: this.props.grid,
+      git_repo_id: actionConfig.git_repo_id,
       kind: 'github',
       kind: 'github',
-      owner: this.props.owner,
-      name: this.props.repoName,
+      owner: actionConfig.git_repo.split('/')[0],
+      name: actionConfig.git_repo.split('/')[1],
     }, (err: any, res: any) => {
     }, (err: any, res: any) => {
       if (err) {
       if (err) {
         console.log(err);
         console.log(err);
@@ -60,9 +59,8 @@ export default class BranchList extends Component<PropsType, StateType> {
       return (
       return (
         <BranchName
         <BranchName
           key={i}
           key={i}
-          isSelected={branch === this.props.selectedBranch}
           lastItem={i === branches.length - 1}
           lastItem={i === branches.length - 1}
-          onClick={() => this.props.setSelectedBranch(branch)}
+          onClick={() => this.props.setBranch(branch)}
         >
         >
           <img src={branch_icon} />{branch}
           <img src={branch_icon} />{branch}
         </BranchName>
         </BranchName>
@@ -85,13 +83,13 @@ const BranchName = styled.div`
   display: flex;
   display: flex;
   width: 100%;
   width: 100%;
   font-size: 13px;
   font-size: 13px;
-  border-bottom: 1px solid ${(props: { lastItem: boolean, isSelected: boolean }) => props.lastItem ? '#00000000' : '#606166'};
+  border-bottom: 1px solid ${(props: { lastItem: boolean }) => props.lastItem ? '#00000000' : '#606166'};
   color: #ffffff;
   color: #ffffff;
   user-select: none;
   user-select: none;
   align-items: center;
   align-items: center;
   padding: 10px 0px;
   padding: 10px 0px;
   cursor: pointer;
   cursor: pointer;
-  background: ${(props: { isSelected: boolean, lastItem: boolean }) => props.isSelected ? '#ffffff22' : '#ffffff11'};
+  background: #ffffff11;
   :hover {
   :hover {
     background: #ffffff22;
     background: #ffffff22;
 
 

+ 171 - 0
dashboard/src/components/repo-selector/ButtonTray.tsx

@@ -0,0 +1,171 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import api from '../../shared/api';
+import { ActionConfigType } from '../../shared/types';
+import { Context } from '../../shared/Context';
+
+type PropsType = {
+  chartName: string | null,
+  chartNamespace: string | null,
+  pathIsSet: boolean,
+  branch: string,
+  actionConfig: ActionConfigType | null,
+  setBranch: (x: string) => void,
+  setActionConfig: (x: ActionConfigType) => void,
+  setPath: (x: boolean) => void,
+};
+
+type StateType = {
+};
+
+export default class RepoSelector extends Component<PropsType, StateType> {
+  createGHAction = () => {
+    let { currentProject, currentCluster } = this.context;
+    let { actionConfig, chartName, chartNamespace } = this.props;
+
+    api.createGHAction('<token>', {
+      git_repo: actionConfig.git_repo,
+      image_repo_uri: actionConfig.image_repo_uri,
+      dockerfile_path: actionConfig.dockerfile_path,
+      git_repo_id: actionConfig.git_repo_id,
+    }, {
+      project_id: currentProject.id,
+      CLUSTER_ID: currentCluster.id,
+      RELEASE_NAME: chartName,
+      RELEASE_NAMESPACE: chartNamespace,
+    }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+      } else {
+        // Exit to initial settings tab
+        console.log(res.data);
+      }
+    });
+  }
+
+  setSelectedRepo = () => {
+    let { actionConfig, setActionConfig } = this.props;
+    let updatedConfig = actionConfig;
+    updatedConfig.git_repo = '';
+    updatedConfig.git_repo_id = null as number;
+    setActionConfig(updatedConfig);
+  }
+
+  goToBranchSelect = () => {
+    let { actionConfig, setActionConfig, setBranch } = this.props;
+    let updatedConfig = actionConfig;
+    updatedConfig.dockerfile_path = '';
+    setBranch('');
+    setActionConfig(updatedConfig);
+  }
+
+  goToPathSelect = () => {
+    let { actionConfig, setActionConfig, setPath } = this.props;
+    let updatedConfig = actionConfig;
+    updatedConfig.image_repo_uri = '';
+    updatedConfig.dockerfile_path = updatedConfig.dockerfile_path.slice(0, -11);
+    setPath(false);
+    setActionConfig(updatedConfig);
+  }
+
+  renderExpanded = () => {
+    let { actionConfig, pathIsSet, branch } = this.props;
+
+    if (!actionConfig.git_repo) {
+      return (
+        <></>
+      );
+    } else if (!branch) {
+      return (
+        <ButtonTray>
+          <BackButton
+            width='130px'
+            onClick={() => this.setSelectedRepo()}
+          >
+            <i className="material-icons">keyboard_backspace</i>
+            Select Repo
+          </BackButton>
+        </ButtonTray>
+      );
+    } else if (!pathIsSet) {
+      return (
+        <ButtonTray>
+          <BackButton
+            onClick={() => this.goToBranchSelect()}
+            width='140px'
+          >
+            <i className="material-icons">keyboard_backspace</i>
+            Select Branch
+          </BackButton>
+        </ButtonTray>  
+      )
+    }
+    return (
+      <ButtonTray>
+        <BackButton
+          width='130px'
+          onClick={() => this.goToPathSelect()}
+        >
+          <i className='material-icons'>keyboard_backspace</i>
+          Select Dockerfile
+        </BackButton>
+        <BackButton
+          disabled={
+            (!actionConfig.git_repo) ||
+            (!actionConfig.dockerfile_path) ||
+            (!actionConfig.image_repo_uri)
+          }
+          width='146px'
+          onClick={() => this.createGHAction()}
+        >
+          <i className='material-icons'>local_shipping</i>
+          Create Github Action
+        </BackButton>
+      </ButtonTray>
+    );
+  }
+
+  render() {
+    return (
+      <>
+        {this.renderExpanded()}
+      </>
+    );
+  }
+}
+
+RepoSelector.contextType = Context;
+
+const BackButton = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 10px;
+  cursor: pointer;
+  font-size: 13px;
+  padding: 5px 10px;
+  border: 1px solid #ffffff55;
+  border-radius: 3px;
+  width: ${(props: { width: string, disabled?: boolean }) => props.width};
+  color: ${(props: { width: string, disabled?: boolean }) => props.disabled ? '#ffffff55' : 'white'};
+  pointer-events: ${(props: { width: string, disabled?: boolean }) => props.disabled ? 'none' : 'auto'};
+
+  :hover {
+    background: #ffffff11;
+  }
+
+  > i {
+    color: ${(props: { width: string, disabled?: boolean }) => props.disabled ? '#ffffff55' : 'white'};
+    font-size: 18px;
+    margin-right: 10px;
+  }
+`;
+
+const ButtonTray = styled.div`
+  margin-top: 10px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+`;

+ 27 - 27
dashboard/src/components/repo-selector/ContentsList.tsx

@@ -7,18 +7,15 @@ import info from '../../assets/info.svg';
 
 
 import api from '../../shared/api';
 import api from '../../shared/api';
 import { Context } from '../../shared/Context';
 import { Context } from '../../shared/Context';
-import { FileType } from '../../shared/types';
+import { FileType, ActionConfigType } from '../../shared/types';
 
 
 import Loading from '../Loading';
 import Loading from '../Loading';
 
 
 type PropsType = {
 type PropsType = {
-  grid: number,
-  repoName: string,
-  owner: string,
-  selectedBranch: string,
-  subdirectory: string,
-  setSubdirectory: (x: string) => void,
-  setDockerfile: () => void,
+  actionConfig: ActionConfigType | null,
+  branch: string,
+  setActionConfig: (x: ActionConfigType) => void,
+  setPath: () => void,
 };
 };
 
 
 type StateType = {
 type StateType = {
@@ -34,17 +31,26 @@ export default class ContentsList extends Component<PropsType, StateType> {
     contents: [] as FileType[]
     contents: [] as FileType[]
   }
   }
 
 
+  setSubdirectory = (x: string) => {
+    let { actionConfig, setActionConfig } = this.props;
+    let updatedConfig = actionConfig;
+    updatedConfig.dockerfile_path = x;
+    setActionConfig(updatedConfig);
+    this.updateContents();
+  }
+
   updateContents = () => {
   updateContents = () => {
+    let { actionConfig, branch } = this.props;
     let { currentProject } = this.context;
     let { currentProject } = this.context;
 
 
     // Get branch contents
     // Get branch contents
-    api.getBranchContents('<token>', { dir: this.props.subdirectory }, {
+    api.getBranchContents('<token>', { dir: actionConfig.dockerfile_path }, {
       project_id: currentProject.id,
       project_id: currentProject.id,
-      git_repo_id: this.props.grid,
+      git_repo_id: actionConfig.git_repo_id,
       kind: 'github',
       kind: 'github',
-      owner: this.props.owner,
-      name: this.props.repoName,
-      branch: this.props.selectedBranch
+      owner: actionConfig.git_repo.split('/')[0],
+      name: actionConfig.git_repo.split('/')[1],
+      branch: branch,
     }, (err: any, res: any) => {
     }, (err: any, res: any) => {
       if (err) {
       if (err) {
         console.log(err);
         console.log(err);
@@ -69,12 +75,6 @@ export default class ContentsList extends Component<PropsType, StateType> {
     this.updateContents();
     this.updateContents();
   }
   }
 
 
-  componentDidUpdate(prevProps: PropsType) {
-    if (this.props.subdirectory !== prevProps.subdirectory) {
-      this.updateContents();  
-    }
-  }
-
   renderContentList = () => {
   renderContentList = () => {
     let { contents, loading, error } = this.state;
     let { contents, loading, error } = this.state;
     if (loading) {
     if (loading) {
@@ -90,9 +90,9 @@ export default class ContentsList extends Component<PropsType, StateType> {
         return (
         return (
           <Item
           <Item
             key={i}
             key={i}
-            isSelected={item.Path === this.props.subdirectory}
+            isSelected={item.Path === this.props.actionConfig.dockerfile_path}
             lastItem={i === contents.length - 1}
             lastItem={i === contents.length - 1}
-            onClick={() => this.props.setSubdirectory(item.Path)}
+            onClick={() => this.setSubdirectory(item.Path)}
           >
           >
             <img src={folder} />
             <img src={folder} />
             {fileName}
             {fileName}
@@ -106,7 +106,7 @@ export default class ContentsList extends Component<PropsType, StateType> {
             key={i}
             key={i}
             lastItem={i === contents.length - 1}
             lastItem={i === contents.length - 1}
             isADocker
             isADocker
-            onClick={() => this.props.setDockerfile()}
+            onClick={() => this.props.setPath()}
           >
           >
             <img src={file} />
             <img src={file} />
             {fileName}
             {fileName}
@@ -126,12 +126,12 @@ export default class ContentsList extends Component<PropsType, StateType> {
   }
   }
 
 
   renderJumpToParent = () => {
   renderJumpToParent = () => {
-    let { subdirectory, setSubdirectory } = this.props;
-    if (subdirectory !== '') {
-      let splits = subdirectory.split('/');
+    let { actionConfig } = this.props;
+    if (actionConfig.dockerfile_path !== '') {
+      let splits = actionConfig.dockerfile_path.split('/');
       let subdir = '';
       let subdir = '';
       if (splits.length !== 1) {
       if (splits.length !== 1) {
-        subdir = subdirectory.replace(splits[splits.length - 1], '');
+        subdir = actionConfig.dockerfile_path.replace(splits[splits.length - 1], '');
         if (subdir.charAt(subdir.length - 1) === '/') {
         if (subdir.charAt(subdir.length - 1) === '/') {
           subdir = subdir.slice(0, subdir.length - 1);
           subdir = subdir.slice(0, subdir.length - 1);
         }
         }
@@ -140,7 +140,7 @@ export default class ContentsList extends Component<PropsType, StateType> {
       return (
       return (
         <Item
         <Item
           lastItem={false}
           lastItem={false}
-          onClick={() => setSubdirectory(subdir)}
+          onClick={() => this.setSubdirectory(subdir)}
         >
         >
           <BackLabel>..</BackLabel>
           <BackLabel>..</BackLabel>
         </Item>
         </Item>

+ 0 - 99
dashboard/src/components/repo-selector/NewGHAction.tsx

@@ -1,99 +0,0 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
-
-import { ChartType } from '../../shared/types';
-import api from '../../shared/api';
-import { Context } from '../../shared/Context';
-import InputRow from '../../components/values-form/InputRow';
-
-import Loading from '../Loading';
-
-type PropsType = {
-  repoName: string,
-  dockerPath: string,
-  grid: number,
-  chart: ChartType,
-  imgURL: string,
-  setURL: (x: string) => void,
-};
-
-type StateType = {
-  trueDockerPath: string,
-  loading: boolean,
-  error: boolean,
-};
-
-export default class NewGHAction extends Component<PropsType, StateType> {
-  state = {
-    dockerRepo: '',
-    trueDockerPath: this.props.dockerPath,
-    loading: false,
-    error: false,
-  }
-
-  componentDidMount() {
-    if (this.props.dockerPath[0] === '/') {
-      this.setState({ trueDockerPath: this.props.dockerPath.substring(1, this.props.dockerPath.length) });
-    }
-  }
-
-  renderConfirmation = () => {
-    let { loading } = this.state;
-    if (loading) {
-      return <LoadingWrapper><Loading /></LoadingWrapper>
-    }
-
-    return (
-      <Holder>
-        <InputRow
-          disabled={true}
-          label='Git Repository'
-          type='text'
-          width='100%'
-          value={this.props.repoName}
-          setValue={(x: string) => console.log(x)}
-        />
-        <InputRow
-          disabled={true}
-          label='Dockerfile Path'
-          type='text'
-          width='100%'
-          value={this.state.trueDockerPath}
-          setValue={(x: string) => console.log(x)}
-        />
-        <InputRow
-          label='Docker Image Repository'
-          placeholder='Image Repo URL (ex. gcr.io/porter/mr-p)'
-          type='text'
-          width='100%'
-          value={this.props.imgURL}
-          setValue={(x: string) => this.props.setURL(x)}
-        />
-      </Holder>
-    )
-  }
-
-  render() {
-    return (
-      <div>
-        {this.renderConfirmation()}
-      </div>
-    );
-  }
-}
-
-NewGHAction.contextType = Context;
-
-const Holder = styled.div`
-  padding: 0px 12px;
-`;
-
-const LoadingWrapper = styled.div`
-  padding: 30px 0px;
-  background: #ffffff11;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-size: 13px;
-  color: #ffffff44;
-`;

+ 36 - 269
dashboard/src/components/repo-selector/RepoSelector.tsx

@@ -3,238 +3,77 @@ import styled from 'styled-components';
 import github from '../../assets/github.png';
 import github from '../../assets/github.png';
 import info from '../../assets/info.svg';
 import info from '../../assets/info.svg';
 
 
-import api from '../../shared/api';
-import { RepoType, ChartType } from '../../shared/types';
+import { RepoType, ChartType, ActionConfigType } from '../../shared/types';
 import { Context } from '../../shared/Context';
 import { Context } from '../../shared/Context';
 
 
-import Loading from '../../components/Loading';
-import BranchList from './BranchList';
-import ContentsList from './ContentsList';
-import NewGHAction from './NewGHAction';
+import ButtonTray from './ButtonTray';
+import ActionConfEditor from './ActionConfEditor';
 
 
 type PropsType = {
 type PropsType = {
   chart: ChartType | null,
   chart: ChartType | null,
   forceExpanded?: boolean,
   forceExpanded?: boolean,
-  selectedRepo: RepoType | null,
-  selectedBranch: string,
-  subdirectory: string,
-  setSelectedRepo: (x: RepoType) => void,
-  setSelectedBranch: (x: string) => void,
-  setSubdirectory: (x: string) => void
+  actionConfig: ActionConfigType | null,
+  setActionConfig: (x: ActionConfigType) => void,
 };
 };
 
 
 type StateType = {
 type StateType = {
   isExpanded: boolean,
   isExpanded: boolean,
-  loading: boolean,
-  error: boolean,
   repos: RepoType[]
   repos: RepoType[]
-  branchGrID: number,
+  branch: string,
+  pathIsSet: boolean,
   dockerfileSelected: boolean,
   dockerfileSelected: boolean,
-  imageURL: string,
 };
 };
 
 
 export default class RepoSelector extends Component<PropsType, StateType> {
 export default class RepoSelector extends Component<PropsType, StateType> {
   state = {
   state = {
     isExpanded: this.props.forceExpanded,
     isExpanded: this.props.forceExpanded,
-    loading: true,
-    error: false,
     repos: [] as RepoType[],
     repos: [] as RepoType[],
-    branchGrID: null as number,
+    branch: '',
+    pathIsSet: false,
     dockerfileSelected: false,
     dockerfileSelected: false,
-    imageURL: null as string,
-  }
-
-  componentDidMount() {
-    let { currentProject } = this.context;
-
-    // Get repos
-    api.getGitRepos('<token>', {
-    }, { project_id: currentProject.id }, (err: any, res: any) => {
-      if (err) {
-        this.setState({ loading: false, error: true });
-      } else {
-        var allRepos: any = [];
-        let counter = 0;
-        for (let i = 0; i < res.data.length; i++) {
-          var grid = res.data[i].id;
-          api.getGitRepoList('<token>', {}, { project_id: currentProject.id, git_repo_id: grid }, (err: any, res: any) => {
-            if (err) {
-              console.log(err);
-              this.setState({ loading: false, error: true });
-            } else {
-              res.data.forEach((repo: any, id: number) => {
-                repo.GHRepoID = grid;
-              })
-              allRepos = allRepos.concat(res.data);
-              this.setState({ repos: allRepos, loading: false, error: false });
-            }
-          })
-        }
-      }
-    });
-  }
-
-  createGHAction = () => {
-    let { currentProject, currentCluster } = this.context;
-    let path = this.props.subdirectory + '/Dockerfile';
-    if (path[0] === '/') {
-      path = path.substring(1, path.length);
-    }
-
-    api.createGHAction('<token>', {
-      git_repo: this.props.selectedRepo.FullName,
-      image_repo_uri: this.state.imageURL,
-      dockerfile_path: path,
-      git_repo_id: this.props.selectedRepo.GHRepoID,
-    }, {
-      project_id: currentProject.id,
-      CLUSTER_ID: currentCluster.id,
-      RELEASE_NAME: this.props.chart.name,
-      RELEASE_NAMESPACE: this.props.chart.namespace,
-    }, (err: any, res: any) => {
-      if (err) {
-        console.log(err);
-        this.setState({ error: true });
-      } else {
-        console.log(res.data);
-      }
-    });
-  }
-
-  renderRepoList = () => {
-    let { repos, loading, error } = this.state;
-    if (loading) {
-      return <LoadingWrapper><Loading /></LoadingWrapper>
-    } else if (error || !repos) {
-      return <LoadingWrapper>Error loading repos.</LoadingWrapper>
-    } else if (repos.length == 0) {
-      return <LoadingWrapper>No connected repos found.</LoadingWrapper>
-    }
-
-    return repos.map((repo: RepoType, i: number) => {
-      return (
-        <RepoName
-          key={i}
-          isSelected={repo === this.props.selectedRepo}
-          lastItem={i === repos.length - 1}
-          onClick={() => this.props.setSelectedRepo(repo)}
-        >
-          <img src={github} />{repo.FullName}
-        </RepoName>
-      );
-    });
   }
   }
 
 
   renderExpanded = () => {
   renderExpanded = () => {
     let {
     let {
-      selectedRepo,
-      selectedBranch,
-      subdirectory,
-      setSelectedRepo,
-      setSelectedBranch,
-      setSubdirectory
+      actionConfig,
+      setActionConfig,
+      chart,
     } = this.props;
     } = this.props;
-
-    if (!selectedRepo) {
-      return (
-        <ExpandedWrapper>
-          {this.renderRepoList()}
-        </ExpandedWrapper>
-      );
-    } else if (selectedBranch === '') {
-      return (
-        <div>
-          <ExpandedWrapperAlt>
-            <BranchList
-              grid={selectedRepo.GHRepoID}
-              setSelectedBranch={(branch: string) => {
-                this.setState({ branchGrID: selectedRepo.GHRepoID });
-                setSelectedBranch(branch);
-              }}
-              repoName={selectedRepo.FullName.split('/')[1]}
-              owner={selectedRepo.FullName.split('/')[0]}
-              selectedBranch={selectedBranch}
-            />
-          </ExpandedWrapperAlt>
-          <ButtonTray>
-            <BackButton
-              width='130px'
-              onClick={() => setSelectedRepo(null)}
-            >
-              <i className="material-icons">keyboard_backspace</i>
-              Select Repo
-            </BackButton>
-          </ButtonTray>
-        </div>
-      );
-    } else if (this.state.dockerfileSelected) {
-      return (
-        <div>
-          <ExpandedWrapperAlt>
-            <NewGHAction
-              repoName={selectedRepo.FullName}
-              dockerPath={subdirectory + '/Dockerfile'}
-              grid={this.state.branchGrID}
-              chart={this.props.chart}
-              imgURL={this.state.imageURL}
-              setURL={(x: string) => this.setState({ imageURL: x })}
-            />
-          </ExpandedWrapperAlt>
-          <ButtonTray>
-            <BackButton
-              width='130px'
-              onClick={() => this.setState({ dockerfileSelected: false })}
-            >
-              <i className='material-icons'>keyboard_backspace</i>
-              Select Dockerfile
-            </BackButton>
-            <BackButton
-              width='146px'
-              onClick={() => this.createGHAction()}
-            >
-              <i className='material-icons'>play_circle_outline</i>
-              Create Github Action
-            </BackButton>
-          </ButtonTray>
-        </div>
-      )
-    }
+    
     return (
     return (
       <div>
       <div>
-        <ExpandedWrapperAlt>
-          <ContentsList
-            grid={this.state.branchGrID}
-            setSubdirectory={(subdirectory: string) => setSubdirectory(subdirectory)}
-            repoName={selectedRepo.FullName.split('/')[1]}
-            owner={selectedRepo.FullName.split('/')[0]}
-            selectedBranch={selectedBranch}
-            subdirectory={subdirectory}
-            setDockerfile={() => this.setState({ dockerfileSelected: true })}
-          />
-        </ExpandedWrapperAlt>
-        <ButtonTray>
-          <BackButton
-            onClick={() => {setSelectedBranch(''); setSubdirectory(''); this.setState({ imageURL: '' })}}
-            width='140px'
-          >
-            <i className="material-icons">keyboard_backspace</i>
-            Select Branch
-          </BackButton>
-        </ButtonTray>
+        <ActionConfEditor
+          actionConfig={actionConfig}
+          branch={this.state.branch}
+          pathIsSet={this.state.pathIsSet}
+          setActionConfig={setActionConfig}
+          setBranch={(branch: string) => this.setState({ branch })}
+          setPath={(pathIsSet: boolean) => this.setState({ pathIsSet })}
+        />
+        <ButtonTray
+          chartName={chart.name}
+          chartNamespace={chart.namespace}
+          pathIsSet={this.state.pathIsSet}
+          branch={this.state.branch}
+          actionConfig={actionConfig}
+          setBranch={(branch: string) => this.setState({ branch })}
+          setActionConfig={setActionConfig}
+          setPath={(pathIsSet: boolean) => this.setState({ pathIsSet })}
+        />
       </div>
       </div>
     );
     );
   }
   }
 
 
   renderSelected = () => {
   renderSelected = () => {
-    let { selectedRepo, subdirectory, selectedBranch } = this.props;
-    if (selectedRepo) {
-      let subdir = subdirectory === '' ? '' : '/' + subdirectory;
+    let { actionConfig } = this.props;
+    if (actionConfig.git_repo) {
+      let subdir = actionConfig.dockerfile_path === '' ? '' : '/' + actionConfig.dockerfile_path;
       return (
       return (
         <RepoLabel>
         <RepoLabel>
           <img src={github} />
           <img src={github} />
-          {selectedRepo.FullName + subdir}
+          {actionConfig.git_repo + subdir}
           <SelectedBranch>
           <SelectedBranch>
-            {!selectedBranch ? '(Select Branch)' : selectedBranch}
+            {!this.state.branch ? '(Select Branch)' : this.state.branch}
           </SelectedBranch>
           </SelectedBranch>
         </RepoLabel>
         </RepoLabel>
       );
       );
@@ -278,75 +117,6 @@ const SelectedBranch = styled.div`
   margin-left: 10px;
   margin-left: 10px;
 `;
 `;
 
 
-const BackButton = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-top: 10px;
-  cursor: pointer;
-  font-size: 13px;
-  padding: 5px 10px;
-  border: 1px solid #ffffff55;
-  border-radius: 3px;
-  width: ${(props: { width: string }) => props.width};
-  color: white;
-
-  :hover {
-    background: #ffffff11;
-  }
-
-  > i {
-    color: white;
-    font-size: 18px;
-    margin-right: 10px;
-  }
-`;
-
-const ButtonTray = styled.div`
-  margin-top: 10px;
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-  align-items: center;
-`;
-
-const RepoName = styled.div`
-  display: flex;
-  width: 100%;
-  font-size: 13px;
-  border-bottom: 1px solid ${(props: { lastItem: boolean, isSelected: boolean }) => props.lastItem ? '#00000000' : '#606166'};
-  color: #ffffff;
-  user-select: none;
-  align-items: center;
-  padding: 10px 0px;
-  cursor: pointer;
-  background: ${(props: { isSelected: boolean, lastItem: boolean }) => props.isSelected ? '#ffffff22' : '#ffffff11'};
-  :hover {
-    background: #ffffff22;
-
-    > i {
-      background: #ffffff22;
-    }
-  }
-
-  > img {
-    width: 18px;
-    height: 18px;
-    margin-left: 12px;
-    margin-right: 12px;
-  }
-`;
-
-const LoadingWrapper = styled.div`
-  padding: 30px 0px;
-  background: #ffffff11;
-  display: flex;
-  align-items: center;
-  font-size: 13px;
-  justify-content: center;
-  color: #ffffff44;
-`;
-
 const ExpandedWrapper = styled.div`
 const ExpandedWrapper = styled.div`
   margin-top: 10px;
   margin-top: 10px;
   width: 100%;
   width: 100%;
@@ -356,9 +126,6 @@ const ExpandedWrapper = styled.div`
   overflow-y: auto;
   overflow-y: auto;
 `;
 `;
 
 
-const ExpandedWrapperAlt = styled(ExpandedWrapper)`
-`;
-
 const RepoLabel = styled.div`
 const RepoLabel = styled.div`
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;

+ 58 - 62
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -21,14 +21,12 @@ type PropsType = {
 };
 };
 
 
 type StateType = {
 type StateType = {
+  actionConfig: ActionConfigType,
   sourceType: string,
   sourceType: string,
   selectedImageUrl: string | null,
   selectedImageUrl: string | null,
   selectedTag: string | null,
   selectedTag: string | null,
   saveValuesStatus: string | null,
   saveValuesStatus: string | null,
   values: string,
   values: string,
-  selectedRepo: RepoType | null,
-  selectedBranch: string,
-  subdirectory: string,
   webhookToken: string,
   webhookToken: string,
   highlightCopyButton: boolean,
   highlightCopyButton: boolean,
   action: ActionConfigType;
   action: ActionConfigType;
@@ -36,14 +34,17 @@ type StateType = {
 
 
 export default class SettingsSection extends Component<PropsType, StateType> {
 export default class SettingsSection extends Component<PropsType, StateType> {
   state = {
   state = {
-    sourceType: 'registry',
+    actionConfig: {
+      git_repo: '',
+      image_repo_uri: '',
+      git_repo_id: 0,
+      dockerfile_path: '',
+    } as ActionConfigType,
+    sourceType: '',
     selectedImageUrl: '',
     selectedImageUrl: '',
     selectedTag: '',
     selectedTag: '',
     values: '',
     values: '',
     saveValuesStatus: null as (string | null),
     saveValuesStatus: null as (string | null),
-    selectedRepo: null as RepoType | null,
-    selectedBranch: '',
-    subdirectory: '',
     webhookToken: '',
     webhookToken: '',
     highlightCopyButton: false,
     highlightCopyButton: false,
     action: {
     action: {
@@ -125,6 +126,40 @@ export default class SettingsSection extends Component<PropsType, StateType> {
     </Helper>
     </Helper>
   */
   */
   renderSourceSection = () => {
   renderSourceSection = () => {
+    if (this.state.action.git_repo.length > 0) {
+      return (
+        <>
+          <Heading>Connected Source</Heading>
+          <Holder>
+            <InputRow
+              disabled={true}
+              label='Git Repository'
+              type='text'
+              width='100%'
+              value={this.state.action.git_repo}
+              setValue={(x: string) => console.log(x)}
+            />
+            <InputRow
+              disabled={true}
+              label='Dockerfile Path'
+              type='text'
+              width='100%'
+              value={this.state.action.dockerfile_path}
+              setValue={(x: string) => console.log(x)}
+            />
+            <InputRow
+              disabled={true}
+              label='Docker Image Repository'
+              type='text'
+              width='100%'
+              value={this.state.action.image_repo_uri}
+              setValue={(x: string) => console.log(x)}
+            />
+          </Holder>
+        </>
+      )
+    }
+
     if (this.state.sourceType === 'registry') {
     if (this.state.sourceType === 'registry') {
       return (
       return (
         <>
         <>
@@ -147,61 +182,22 @@ export default class SettingsSection extends Component<PropsType, StateType> {
     let { currentProject } = this.context;
     let { currentProject } = this.context;
     return (
     return (
       <>
       <>
-        {this.state.action.git_repo.length > 0
-          ?
-          <>
-            <Heading>Connected Source</Heading>
-            <Holder>
-              <InputRow
-                disabled={true}
-                label='Git Repository'
-                type='text'
-                width='100%'
-                value={this.state.action.git_repo}
-                setValue={(x: string) => console.log(x)}
-              />
-              <InputRow
-                disabled={true}
-                label='Dockerfile Path'
-                type='text'
-                width='100%'
-                value={this.state.action.dockerfile_path}
-                setValue={(x: string) => console.log(x)}
-              />
-              <InputRow
-                disabled={true}
-                label='Docker Image Repository'
-                type='text'
-                width='100%'
-                value={this.state.action.image_repo_uri}
-                setValue={(x: string) => console.log(x)}
-              />
-            </Holder>
-          </>
-          :
-          <>
-            <Heading>Connect a Source</Heading>
-            <Helper>
-              Select a repo to connect to. You can 
-              <A padRight={true} href={`/api/oauth/projects/${currentProject.id}/github?redirected=true`}>
-                log in with GitHub
-              </A> or
-              <Highlight onClick={() => this.setState({ sourceType: 'registry' })}>
-                link an image registry
-              </Highlight>.
-            </Helper>
-            <RepoSelector
-              chart={this.props.currentChart}
-              forceExpanded={true}
-              selectedRepo={this.state.selectedRepo}
-              selectedBranch={this.state.selectedBranch}
-              subdirectory={this.state.subdirectory}
-              setSelectedRepo={(x: RepoType) => this.setState({ selectedRepo: x })}
-              setSelectedBranch={(x: string) => this.setState({ selectedBranch: x })}
-              setSubdirectory={(x: string) => this.setState({ subdirectory: x })}
-            />
-          </>
-        }
+        <Heading>Connect a Source</Heading>
+        <Helper>
+          Select a repo to connect to. You can 
+          <A padRight={true} href={`/api/oauth/projects/${currentProject.id}/github?redirected=true`}>
+            log in with GitHub
+          </A> or
+          <Highlight onClick={() => this.setState({ sourceType: 'registry' })}>
+            link an image registry
+          </Highlight>.
+        </Helper>
+        <RepoSelector
+          chart={this.props.currentChart}
+          forceExpanded={true}
+          actionConfig={this.state.actionConfig}
+          setActionConfig={(actionConfig: ActionConfigType) => this.setState({ actionConfig })}
+        />
       </>
       </>
     );
     );
   }
   }

+ 0 - 2
dashboard/src/main/home/integrations/IntegrationList.tsx

@@ -18,8 +18,6 @@ type StateType = {
 export default class IntegrationList extends Component<PropsType, StateType> {
 export default class IntegrationList extends Component<PropsType, StateType> {
   renderContents = () => {
   renderContents = () => {
     let { integrations, titles, setCurrent, isCategory } = this.props;
     let { integrations, titles, setCurrent, isCategory } = this.props;
-    console.log(`titles: ${titles}`);
-    console.log(`integrations: ${integrations}`);
     if (titles && titles.length > 0) {
     if (titles && titles.length > 0) {
       return integrations.map((integration: string, i: number) => {
       return integrations.map((integration: string, i: number) => {
         let icon = integrationList[integration] && integrationList[integration].icon;
         let icon = integrationList[integration] && integrationList[integration].icon;

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

@@ -30,8 +30,8 @@ export default class GCRForm extends Component<PropsType, StateType> {
   }
   }
 
 
   isDisabled = (): boolean => {
   isDisabled = (): boolean => {
-    let { credentialsName, serviceAccountKey } = this.state;
-    if (credentialsName === '' || serviceAccountKey === '') {
+    let { gcpRegion, gcpProjectID, serviceAccountKey } = this.state;
+    if (gcpRegion  === '' || serviceAccountKey === '' || gcpProjectID === '') {
       return true;
       return true;
     }
     }
     return false;
     return false;
@@ -50,7 +50,17 @@ export default class GCRForm extends Component<PropsType, StateType> {
       if (err) {
       if (err) {
         console.log(err);
         console.log(err);
       } else {
       } else {
-        console.log(res.data);
+        api.createGCR('<token>', {
+          gcp_integration_id: res.data.id,
+        }, {
+          project_id: currentProject.id,
+        }, (err: any, res: any) => {
+          if (err) {
+            console.log(err);
+          } else {
+            this.props.closeForm();
+          }
+        })
       }
       }
     })
     })
   }
   }
@@ -59,16 +69,6 @@ export default class GCRForm extends Component<PropsType, StateType> {
     return ( 
     return ( 
       <StyledForm>
       <StyledForm>
         <CredentialWrapper>
         <CredentialWrapper>
-          <Heading>Porter Settings</Heading>
-          <Helper>Give a name to this set of registry credentials (just for Porter).</Helper>
-          <InputRow
-            type='text'
-            value={this.state.credentialsName}
-            setValue={(x: string) => this.setState({ credentialsName: x })}
-            label='🏷️ Registry Name'
-            placeholder='ex: paper-straw'
-            width='100%'
-          />
           <Heading>GCP Settings</Heading>
           <Heading>GCP Settings</Heading>
           <Helper>Service account credentials for GCP permissions.</Helper>
           <Helper>Service account credentials for GCP permissions.</Helper>
           <InputRow
           <InputRow
@@ -90,8 +90,8 @@ export default class GCRForm extends Component<PropsType, StateType> {
             type='text'
             type='text'
             value={this.state.gcpProjectID}
             value={this.state.gcpProjectID}
             setValue={(x: string) => this.setState({ gcpProjectID: x })}
             setValue={(x: string) => this.setState({ gcpProjectID: x })}
-            label='GCP Project ID'
-            placeholder='ex: porter-dev-273614'
+            label='📝 GCP Project ID'
+            placeholder='ex: skynet-dev-172969'
             width='100%'
             width='100%'
           />
           />
         </CredentialWrapper>
         </CredentialWrapper>

+ 4 - 4
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -116,12 +116,12 @@ export default class Sidebar extends Component<PropsType, StateType> {
           </NavButton>
           </NavButton>
           <NavButton
           <NavButton
             selected={currentView === 'integrations'}
             selected={currentView === 'integrations'}
-            //onClick={() => {
-            //  setCurrentView('integrations')
-           // }}
             onClick={() => {
             onClick={() => {
-              setCurrentModal('IntegrationsInstructionsModal', {})
+              setCurrentView('integrations')
             }}
             }}
+            /*onClick={() => {
+              setCurrentModal('IntegrationsInstructionsModal', {})
+            }}*/
           >
           >
             <Img src={integrations} />
             <Img src={integrations} />
             Integrations
             Integrations

+ 126 - 21
dashboard/src/main/home/templates/expanded-template/LaunchTemplate.tsx

@@ -6,9 +6,10 @@ import _ from 'lodash';
 import { Context } from '../../../../shared/Context';
 import { Context } from '../../../../shared/Context';
 import api from '../../../../shared/api';
 import api from '../../../../shared/api';
 
 
-import { PorterTemplate, ChoiceType, ClusterType, StorageType } from '../../../../shared/types';
+import { PorterTemplate, ChoiceType, ClusterType, StorageType, ActionConfigType } from '../../../../shared/types';
 import Selector from '../../../../components/Selector';
 import Selector from '../../../../components/Selector';
 import ImageSelector from '../../../../components/image-selector/ImageSelector';
 import ImageSelector from '../../../../components/image-selector/ImageSelector';
+import ActionConfEditor from '../../../../components/repo-selector/ActionConfEditor';
 import TabRegion from '../../../../components/TabRegion';
 import TabRegion from '../../../../components/TabRegion';
 import InputRow from '../../../../components/values-form/InputRow';
 import InputRow from '../../../../components/values-form/InputRow';
 import SaveButton from '../../../../components/SaveButton';
 import SaveButton from '../../../../components/SaveButton';
@@ -31,12 +32,16 @@ type StateType = {
   selectedNamespace: string,
   selectedNamespace: string,
   selectedCluster: string,
   selectedCluster: string,
   selectedImageUrl: string | null,
   selectedImageUrl: string | null,
+  sourceType: string,
   selectedTag: string | null,
   selectedTag: string | null,
   templateName: string,
   templateName: string,
   tabOptions: ChoiceType[],
   tabOptions: ChoiceType[],
   currentTab: string | null,
   currentTab: string | null,
   tabContents: any
   tabContents: any
   namespaceOptions: { label: string, value: string }[],
   namespaceOptions: { label: string, value: string }[],
+  actionConfig: ActionConfigType,
+  branch: string,
+  pathIsSet: boolean,
 };
 };
 
 
 export default class LaunchTemplate extends Component<PropsType, StateType> {
 export default class LaunchTemplate extends Component<PropsType, StateType> {
@@ -47,14 +52,47 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
     selectedCluster: this.context.currentCluster.name,
     selectedCluster: this.context.currentCluster.name,
     selectedNamespace: "default",
     selectedNamespace: "default",
     selectedImageUrl: '' as string | null,
     selectedImageUrl: '' as string | null,
+    sourceType: 'registry',
     templateName: '',
     templateName: '',
     selectedTag: '' as string | null,
     selectedTag: '' as string | null,
     tabOptions: [] as ChoiceType[],
     tabOptions: [] as ChoiceType[],
     currentTab: null as string | null,
     currentTab: null as string | null,
     tabContents: [] as any,
     tabContents: [] as any,
     namespaceOptions: [] as { label: string, value: string }[],
     namespaceOptions: [] as { label: string, value: string }[],
+    actionConfig: {
+      git_repo: '',
+      image_repo_uri: '',
+      git_repo_id: 0,
+      dockerfile_path: '',
+    } as ActionConfigType,
+    branch: '',
+    pathIsSet: false,
   };
   };
 
 
+  createGHAction = (chartName: string, chartNamespace: string) => {
+    let { currentProject, currentCluster } = this.context;
+    let { actionConfig } = this.state;
+
+    api.createGHAction('<token>', {
+      git_repo: actionConfig.git_repo,
+      image_repo_uri: actionConfig.image_repo_uri,
+      dockerfile_path: actionConfig.dockerfile_path,
+      git_repo_id: actionConfig.git_repo_id,
+    }, {
+      project_id: currentProject.id,
+      CLUSTER_ID: currentCluster.id,
+      RELEASE_NAME: chartName,
+      RELEASE_NAMESPACE: chartNamespace,
+    }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+      } else {
+        // Exit to initial settings tab
+        console.log(res.data);
+      }
+    });
+  }
+
   onSubmitAddon = (wildcard?: any) => {
   onSubmitAddon = (wildcard?: any) => {
     let { currentCluster, currentProject } = this.context;
     let { currentCluster, currentProject } = this.context;
     let name = this.state.templateName || randomWords({ exactly: 3, join: '-' });
     let name = this.state.templateName || randomWords({ exactly: 3, join: '-' });
@@ -87,7 +125,11 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
         })
         })
       } else {
       } else {
         // this.props.setCurrentView('cluster-dashboard');
         // this.props.setCurrentView('cluster-dashboard');
-        this.setState({ saveValuesStatus: 'successful' });
+        this.setState({ saveValuesStatus: 'successful' }, () => {
+          if (this.state.sourceType !== 'registry') {
+            this.createGHAction(name, this.state.selectedNamespace);
+          }
+        });
         posthog.capture('Deployed template', {
         posthog.capture('Deployed template', {
           name: this.props.currentTemplate.name,
           name: this.props.currentTemplate.name,
           namespace: this.state.selectedNamespace,
           namespace: this.state.selectedNamespace,
@@ -145,7 +187,11 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
         })
         })
       } else {
       } else {
         // this.props.setCurrentView('cluster-dashboard');
         // this.props.setCurrentView('cluster-dashboard');
-        this.setState({ saveValuesStatus: 'successful' });
+        this.setState({ saveValuesStatus: 'successful' }, () => {
+          if (this.state.sourceType !== 'registry') {
+            this.createGHAction(name, this.state.selectedNamespace);
+          }
+        });
         posthog.capture('Deployed template', {
         posthog.capture('Deployed template', {
           name: this.props.currentTemplate.name,
           name: this.props.currentTemplate.name,
           namespace: this.state.selectedNamespace,
           namespace: this.state.selectedNamespace,
@@ -284,25 +330,68 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
 
 
   // Display if current template uses source (image or repo)
   // Display if current template uses source (image or repo)
   renderSourceSelector = () => {
   renderSourceSelector = () => {
+    let { currentProject } = this.context;
+
     if (this.props.form?.hasSource) {
     if (this.props.form?.hasSource) {
-      return (
-        <>
-          <Subtitle>
-            Select the container image you would like to connect to this template.
-            <Required>*</Required>
-          </Subtitle>
-          <DarkMatter />
-          <ImageSelector
-            selectedTag={this.state.selectedTag}
-            selectedImageUrl={this.state.selectedImageUrl}
-            setSelectedImageUrl={this.setSelectedImageUrl}
-            setSelectedTag={(x: string) => this.setState({ selectedTag: x })}
-            forceExpanded={true}
-            setCurrentView={this.props.setCurrentView}
-          />
-          <br />
-        </>
-      );
+      if (this.state.sourceType === 'registry') {
+        return (
+          <>
+            <Subtitle>
+              Select the container image you would like to connect to this template or
+              <Highlight onClick={() => this.setState({ sourceType: 'repo' })}>
+                link a git repository
+              </Highlight>.
+              <Required>*</Required>
+            </Subtitle>
+            <DarkMatter />
+            <ImageSelector
+              selectedTag={this.state.selectedTag}
+              selectedImageUrl={this.state.selectedImageUrl}
+              setSelectedImageUrl={this.setSelectedImageUrl}
+              setSelectedTag={(x: string) => this.setState({ selectedTag: x })}
+              forceExpanded={true}
+              setCurrentView={this.props.setCurrentView}
+            />
+            <br />
+          </>
+        )
+      } else {
+        return (
+          <>
+            <Subtitle>
+              Select a repo to connect to. You can 
+              <A padRight={true} href={`/api/oauth/projects/${currentProject.id}/github?redirected=true`}>
+                log in with GitHub
+              </A> or
+              <Highlight
+                onClick={() => this.setState({
+                  sourceType: 'registry',
+                  actionConfig: {
+                    git_repo: '',
+                    image_repo_uri: '',
+                    git_repo_id: 0,
+                    dockerfile_path: '',
+                  } as ActionConfigType
+                })}
+              >
+                link an image registry
+              </Highlight>.
+              <Required>*</Required>
+            </Subtitle>
+            <ActionConfEditor
+              actionConfig={this.state.actionConfig}
+              branch={this.state.branch}
+              pathIsSet={this.state.pathIsSet}
+              setActionConfig={(actionConfig: ActionConfigType) => this.setState({ actionConfig }, () => {
+                this.setSelectedImageUrl(this.state.actionConfig.image_repo_uri);
+              })}
+              setBranch={(branch: string) => this.setState({ branch })}
+              setPath={(pathIsSet: boolean) => this.setState({ pathIsSet })}
+            />
+            <br />
+          </>
+        )
+      }
     }
     }
   }
   }
 
 
@@ -517,4 +606,20 @@ const TitleSection = styled.div`
 const StyledLaunchTemplate = styled.div`
 const StyledLaunchTemplate = styled.div`
   width: 100%;
   width: 100%;
   padding-bottom: 150px;
   padding-bottom: 150px;
+`;
+
+const Highlight = styled.div`
+  color: #8590ff;
+  text-decoration: underline;
+  margin-left: 5px;
+  cursor: pointer;
+  padding-right: ${(props: { padRight?: boolean }) => props.padRight ? '5px' : ''};
+`;
+
+const A = styled.a`
+  color: #8590ff;
+  text-decoration: underline;
+  margin-left: 5px;
+  cursor: pointer;
+  padding-right: ${(props: { padRight?: boolean }) => props.padRight ? '5px' : ''};
 `;
 `;

+ 1 - 1
internal/config/config.go

@@ -30,7 +30,7 @@ type ServerConf struct {
 	IsLocal              bool          `env:"IS_LOCAL,default=false"`
 	IsLocal              bool          `env:"IS_LOCAL,default=false"`
 	IsTesting            bool          `env:"IS_TESTING,default=false"`
 	IsTesting            bool          `env:"IS_TESTING,default=false"`
 
 
-	DefaultHelmRepoURL string `env:"HELM_REPO_URL,default=https://porter-dev.github.io/chart-repo-dev/"`
+	DefaultHelmRepoURL string `env:"HELM_REPO_URL,default=https://porter-dev.github.io/chart-repo/"`
 
 
 	GithubClientID     string `env:"GITHUB_CLIENT_ID"`
 	GithubClientID     string `env:"GITHUB_CLIENT_ID"`
 	GithubClientSecret string `env:"GITHUB_CLIENT_SECRET"`
 	GithubClientSecret string `env:"GITHUB_CLIENT_SECRET"`