Ver código fonte

Merge pull request #286 from porter-dev/beta.3.cicd

Beta.3.cicd
abelanger5 5 anos atrás
pai
commit
4b790333cc

+ 3 - 1
dashboard/src/components/image-selector/ImageList.tsx

@@ -14,6 +14,7 @@ type PropsType = {
   selectedTag: string | null;
   clickedImage: ImageType | null;
   registry?: any;
+  noTagSelection?: boolean;
   setSelectedImageUrl: (x: string) => void;
   setSelectedTag: (x: string) => void;
   setClickedImage: (x: ImageType) => void;
@@ -216,7 +217,8 @@ export default class ImageSelector extends Component<PropsType, StateType> {
 
   renderExpanded = () => {
     let { selectedTag, selectedImageUrl, setSelectedTag } = this.props;
-    if (!this.props.clickedImage) {
+
+    if (!this.props.clickedImage || this.props.noTagSelection) {
       return (
         <div>
           <ExpandedWrapper>{this.renderImageList()}</ExpandedWrapper>

+ 4 - 28
dashboard/src/components/image-selector/ImageSelector.tsx

@@ -18,6 +18,7 @@ type PropsType = {
   selectedTag: string | null;
   setSelectedImageUrl: (x: string) => void;
   setSelectedTag: (x: string) => void;
+  noTagSelection?: boolean;
 };
 
 type StateType = {
@@ -173,32 +174,6 @@ export default class ImageSelector extends Component<PropsType, StateType> {
     }
   };
 
-  renderExpanded = () => {
-    let { selectedTag, selectedImageUrl, setSelectedTag } = this.props;
-    if (!this.state.clickedImage) {
-      return (
-        <div>
-          <ExpandedWrapper>{this.renderImageList()}</ExpandedWrapper>
-          {this.renderBackButton()}
-        </div>
-      );
-    } else {
-      return (
-        <div>
-          <ExpandedWrapper>
-            <TagList
-              selectedTag={selectedTag}
-              selectedImageUrl={selectedImageUrl}
-              setSelectedTag={setSelectedTag}
-              registryId={this.state.clickedImage.registryId}
-            />
-          </ExpandedWrapper>
-          {this.renderBackButton()}
-        </div>
-      );
-    }
-  };
-
   renderSelected = () => {
     let { selectedImageUrl, setSelectedImageUrl } = this.props;
     let { clickedImage } = this.state;
@@ -257,6 +232,7 @@ export default class ImageSelector extends Component<PropsType, StateType> {
             selectedImageUrl={this.props.selectedImageUrl}
             selectedTag={this.props.selectedTag}
             clickedImage={this.state.clickedImage}
+            noTagSelection={this.props.noTagSelection}
             setSelectedImageUrl={this.props.setSelectedImageUrl}
             setSelectedTag={this.props.setSelectedTag}
             setClickedImage={(x: ImageType) =>
@@ -319,7 +295,7 @@ const ImageItem = styled.div`
   font-size: 13px;
   border-bottom: 1px solid
     ${(props: { lastItem: boolean; isSelected: boolean }) =>
-      props.lastItem ? "#00000000" : "#606166"};
+    props.lastItem ? "#00000000" : "#606166"};
   color: #ffffff;
   user-select: none;
   align-items: center;
@@ -378,7 +354,7 @@ const Label = styled.div`
 
 const StyledImageSelector = styled.div`
   width: 100%;
-  margin-top: 22px;
+  margin-top: 10px;
   border: 1px solid #ffffff55;
   background: ${(props: { isExpanded: boolean; forceExpanded: boolean }) =>
     props.isExpanded ? "#ffffff11" : ""};

+ 82 - 43
dashboard/src/components/repo-selector/ActionConfEditor.tsx

@@ -1,33 +1,34 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
+import React, { Component } from "react";
+import styled from "styled-components";
 
-import { ActionConfigType } from '../../shared/types';
-import { Context } from '../../shared/Context';
+import { ActionConfigType } from "shared/types";
+import { Context } from "shared/Context";
 
-import RepoList from './RepoList';
-import BranchList from './BranchList';
-import ContentsList from './ContentsList';
-import ActionDetails from './ActionDetails';
+import RepoList from "./RepoList";
+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,
+  actionConfig: ActionConfigType | null;
+  branch: string;
+  pathIsSet: boolean;
+  setActionConfig: (x: ActionConfigType) => void;
+  setBranch: (x: string) => void;
+  setPath: (x: boolean) => void;
+  reset: () => void;
 };
 
 type StateType = {
-  loading: boolean,
-  error: boolean,
+  loading: boolean;
+  error: boolean;
 };
 
 export default class ActionConfEditor extends Component<PropsType, StateType> {
   state = {
     loading: true,
     error: false,
-  }
+  };
 
   renderExpanded = () => {
     let {
@@ -51,41 +52,55 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
       );
     } else if (!branch) {
       return (
-        <ExpandedWrapperAlt>
-          <BranchList
-            actionConfig={actionConfig}
-            setBranch={(branch: string) => setBranch(branch)}
-          />
-        </ExpandedWrapperAlt>
+        <>
+          <ExpandedWrapperAlt>
+            <BranchList
+              actionConfig={actionConfig}
+              setBranch={(branch: string) => setBranch(branch)}
+            />
+          </ExpandedWrapperAlt>
+          {this.renderResetButton()}
+        </>
       );
     } else if (!pathIsSet) {
       return (
+        <>
+          <ExpandedWrapperAlt>
+            <ContentsList
+              actionConfig={actionConfig}
+              branch={branch}
+              setActionConfig={setActionConfig}
+              setPath={() => setPath(true)}
+            />
+          </ExpandedWrapperAlt>
+          {this.renderResetButton()}
+        </>
+      );
+    }
+    return (
+      <>
         <ExpandedWrapperAlt>
-          <ContentsList
+          <ActionDetails
             actionConfig={actionConfig}
-            branch={branch}
             setActionConfig={setActionConfig}
-            setPath={() => setPath(true)}
           />
         </ExpandedWrapperAlt>
-      );
-    }
-    return (
-      <ExpandedWrapperAlt>
-        <ActionDetails
-          actionConfig={actionConfig}
-          setActionConfig={setActionConfig}
-        />
-      </ExpandedWrapperAlt>
-    )
-  }
+        {this.renderResetButton()}
+      </>
+    );
+  };
 
-  render() {
+  renderResetButton = () => {
     return (
-      <>
-        {this.renderExpanded()}
-      </>
+      <BackButton width="150px" onClick={this.props.reset}>
+        <i className="material-icons">keyboard_backspace</i>
+        Reset Selection
+      </BackButton>
     );
+  };
+
+  render() {
+    return <>{this.renderExpanded()}</>;
   }
 }
 
@@ -100,5 +115,29 @@ const ExpandedWrapper = styled.div`
   overflow-y: auto;
 `;
 
-const ExpandedWrapperAlt = styled(ExpandedWrapper)`
-`;
+const ExpandedWrapperAlt = styled(ExpandedWrapper)``;
+
+const BackButton = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 10px;
+  cursor: pointer;
+  font-size: 13px;
+  padding: 5px 13px;
+  border: 1px solid #ffffff55;
+  border-radius: 3px;
+  width: ${(props: { width: string }) => props.width};
+  color: white;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+  }
+
+  > i {
+    color: white;
+    font-size: 16px;
+    margin-right: 6px;
+  }
+`;

+ 63 - 41
dashboard/src/components/repo-selector/ActionDetails.tsx

@@ -1,31 +1,32 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
+import ImageSelector from "components/image-selector/ImageSelector";
+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';
+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,
+  actionConfig: ActionConfigType | null;
+  setActionConfig: (x: ActionConfigType) => void;
 };
 
 type StateType = {
-  dockerRepo: string,
-  error: boolean,
+  dockerRepo: string;
+  error: boolean;
 };
 
 export default class ActionDetails extends Component<PropsType, StateType> {
   state = {
-    dockerRepo: '',
+    dockerRepo: "",
     error: false,
-  }
+  };
 
   componentDidMount() {
     if (this.props.actionConfig.dockerfile_path) {
-      this.setPath('/Dockerfile');
+      this.setPath("/Dockerfile");
     } else {
-      this.setPath('Dockerfile');
+      this.setPath("Dockerfile");
     }
   }
 
@@ -34,58 +35,79 @@ export default class ActionDetails extends Component<PropsType, StateType> {
     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;
+    var imageComponent
+
+    if (!this.props.actionConfig.image_repo_uri) {
+      imageComponent = <div>
+          <Label>Target Image URL</Label>
+          <ImageSelector
+            selectedTag="latest"
+            selectedImageUrl={this.props.actionConfig.image_repo_uri}
+            setSelectedImageUrl={this.setURL}
+            setSelectedTag={() => null}
+            forceExpanded={true}
+            noTagSelection={true}
+          />
+        </div>
+    } else {
+      imageComponent = <InputRow
+        disabled={true}
+        label="Target Image URL"
+        type="text"
+        width="100%"
+        value={this.props.actionConfig.image_repo_uri}
+        setValue={(x: string) => console.log(x)}
+      />
+    }
+
     return (
       <Holder>
         <InputRow
           disabled={true}
-          label='Git Repository'
-          type='text'
-          width='100%'
-          value={actionConfig.git_repo}
+          label="Git Repository"
+          type="text"
+          width="100%"
+          value={this.props.actionConfig.git_repo}
           setValue={(x: string) => console.log(x)}
         />
         <InputRow
           disabled={true}
-          label='Dockerfile Path'
-          type='text'
-          width='100%'
-          value={actionConfig.dockerfile_path}
+          label="Dockerfile Path"
+          type="text"
+          width="100%"
+          value={this.props.actionConfig.dockerfile_path}
           setValue={(x: string) => console.log(x)}
         />
-        <InputRow
-          label='Docker Image Repository'
-          placeholder='Image Repo URI (ex. my-repo/image)'
-          type='text'
-          width='100%'
-          value={actionConfig.image_repo_uri}
-          setValue={(x: string) => this.setURL(x)}
-        />
+        {imageComponent}
       </Holder>
-    )
-  }
+    );
+  };
 
   render() {
-    return (
-      <div>
-        {this.renderConfirmation()}
-      </div>
-    );
+    return <div>{this.renderConfirmation()}</div>;
   }
 }
 
+const Label = styled.div`
+  color: #ffffff;
+  display: flex;
+  align-items: center;
+  font-size: 13px;
+  font-family: "Work Sans", sans-serif;
+`;
+
 ActionDetails.contextType = Context;
 
 const Holder = styled.div`
-  padding: 0px 12px;
-`;
+  padding: 0px 12px 24px 12px;
+`;

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

@@ -160,7 +160,7 @@ export default class ContentsList extends Component<PropsType, StateType> {
     return (
       <FileItem lastItem={false}>
         <img src={info} />
-        Select subfolder (Optional)
+        Select path to Dockerfile
       </FileItem>
     );
   };

+ 22 - 2
dashboard/src/components/repo-selector/RepoList.tsx

@@ -66,7 +66,7 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
             this.setState({ loading: false, error: false });
           }
         })
-        .catch((err) => this.setState({ loading: false, error: true }));
+        .catch((_) => this.setState({ loading: false, error: true }));
     } else {
       let grid = this.props.userId;
       api
@@ -107,7 +107,18 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
     } else if (error || !repos) {
       return <LoadingWrapper>Error loading repos.</LoadingWrapper>;
     } else if (repos.length == 0) {
-      return <LoadingWrapper>No connected repos found.</LoadingWrapper>;
+      return (
+        <LoadingWrapper>
+          No connected Github repos found. You can
+          <A
+            padRight={true}
+            href={`/api/oauth/projects/${this.context.currentProject.id}/github?redirected=true`}
+          >
+            log in with GitHub
+          </A>{" "}
+          .
+        </LoadingWrapper>
+      );
     }
 
     return repos.map((repo: RepoType, i: number) => {
@@ -205,3 +216,12 @@ const ExpandedWrapperAlt = styled(ExpandedWrapper)`
   max-height: 275px;
   overflow-y: auto;
 `;
+
+const A = styled.a`
+  color: #8590ff;
+  text-decoration: underline;
+  margin-left: 5px;
+  cursor: pointer;
+  padding-right: ${(props: { padRight?: boolean }) =>
+    props.padRight ? "5px" : ""};
+`;

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

@@ -1,44 +1,41 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
-import github from 'assets/github.png';
-import info from 'assets/info.svg';
-import { RepoType, ChartType, ActionConfigType } from '../../shared/types';
-import { Context } from '../../shared/Context';
+import React, { Component } from "react";
+import styled from "styled-components";
+import github from "assets/github.png";
+import info from "assets/info.svg";
+import { RepoType, ChartType, ActionConfigType } from "shared/types";
+import { Context } from "shared/Context";
 
-import ButtonTray from './ButtonTray';
-import ActionConfEditor from './ActionConfEditor';
+import ButtonTray from "./ButtonTray";
+import ActionConfEditor from "./ActionConfEditor";
 
 type PropsType = {
-  chart: ChartType | null,
-  forceExpanded?: boolean,
-  actionConfig: ActionConfigType | null,
-  setActionConfig: (x: ActionConfigType) => void,
+  chart: ChartType | null;
+  forceExpanded?: boolean;
+  actionConfig: ActionConfigType | null;
+  setActionConfig: (x: ActionConfigType) => void;
+  resetActionConfig: () => void;
 };
 
 type StateType = {
-  isExpanded: boolean,
-  repos: RepoType[]
-  branch: string,
-  pathIsSet: boolean,
-  dockerfileSelected: boolean,
+  isExpanded: boolean;
+  repos: RepoType[];
+  branch: string;
+  pathIsSet: boolean;
+  dockerfileSelected: boolean;
 };
 
 export default class RepoSelector extends Component<PropsType, StateType> {
   state = {
     isExpanded: this.props.forceExpanded,
     repos: [] as RepoType[],
-    branch: '',
+    branch: "",
     pathIsSet: false,
     dockerfileSelected: false,
-  }
+  };
 
   renderExpanded = () => {
-    let {
-      actionConfig,
-      setActionConfig,
-      chart,
-    } = this.props;
-    
+    let { actionConfig, setActionConfig, chart } = this.props;
+
     return (
       <div>
         <ActionConfEditor
@@ -48,6 +45,14 @@ export default class RepoSelector extends Component<PropsType, StateType> {
           setActionConfig={setActionConfig}
           setBranch={(branch: string) => this.setState({ branch })}
           setPath={(pathIsSet: boolean) => this.setState({ pathIsSet })}
+          reset={() => {
+            this.setState({
+              branch: "",
+              pathIsSet: false,
+              dockerfileSelected: false,
+            });
+            this.props.resetActionConfig();
+          }}
         />
         <ButtonTray
           chartName={chart.name}
@@ -66,13 +71,16 @@ export default class RepoSelector extends Component<PropsType, StateType> {
   renderSelected = () => {
     let { actionConfig } = this.props;
     if (actionConfig.git_repo) {
-      let subdir = actionConfig.dockerfile_path === '' ? '' : '/' + actionConfig.dockerfile_path;
+      let subdir =
+        actionConfig.dockerfile_path === ""
+          ? ""
+          : "/" + actionConfig.dockerfile_path;
       return (
         <RepoLabel>
           <img src={github} />
           {actionConfig.git_repo + subdir}
           <SelectedBranch>
-            {!this.state.branch ? '(Select Branch)' : this.state.branch}
+            {!this.state.branch ? "(Select Branch)" : this.state.branch}
           </SelectedBranch>
         </RepoLabel>
       );
@@ -120,15 +128,6 @@ const SelectedBranch = styled.div`
   margin-left: 10px;
 `;
 
-const ExpandedWrapper = styled.div`
-  margin-top: 10px;
-  width: 100%;
-  border-radius: 3px;
-  border: 1px solid #ffffff44;
-  max-height: 275px;
-  overflow-y: auto;
-`;
-
 const RepoLabel = styled.div`
   display: flex;
   align-items: center;

+ 30 - 28
dashboard/src/main/home/Home.tsx

@@ -125,38 +125,40 @@ class Home extends Component<PropsType, StateType> {
       .catch(console.log);
   };
 
-  provisionDOCR = (integrationId: number, tier: string, callback?: any) => {
+  provisionDOCR = async (
+    integrationId: number,
+    tier: string,
+    callback?: any
+  ) => {
     console.log("Provisioning DOCR...");
-    return api
-      .createDOCR(
-        "<token>",
-        {
-          do_integration_id: integrationId,
-          docr_name: this.props.currentProject.name,
-          docr_subscription_tier: tier,
-        },
-        {
-          project_id: this.props.currentProject.id,
-        }
-      )
-      .then(() => callback());
+    await api.createDOCR(
+      "<token>",
+      {
+        do_integration_id: integrationId,
+        docr_name: this.props.currentProject.name,
+        docr_subscription_tier: tier,
+      },
+      {
+        project_id: this.props.currentProject.id,
+      }
+    );
+    return callback();
   };
 
-  provisionDOKS = (integrationId: number, region: string) => {
+  provisionDOKS = async (integrationId: number, region: string) => {
     console.log("Provisioning DOKS...");
-    return api
-      .createDOKS(
-        "<token>",
-        {
-          do_integration_id: integrationId,
-          doks_name: this.props.currentProject.name,
-          do_region: region,
-        },
-        {
-          project_id: this.props.currentProject.id,
-        }
-      )
-      .then(() => this.props.history.push("dashboard?tab=provisioner"));
+    await api.createDOKS(
+      "<token>",
+      {
+        do_integration_id: integrationId,
+        doks_name: this.props.currentProject.name,
+        do_region: region,
+      },
+      {
+        project_id: this.props.currentProject.id,
+      }
+    );
+    return this.props.history.push("dashboard?tab=provisioner");
   };
 
   checkDO = () => {

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -109,8 +109,8 @@ class ClusterDashboard extends Component<PropsType, StateType> {
         <LineBreak />
 
         <ControlRow>
-          <Button onClick={() => this.props.history.push("templates")}>
-            <i className="material-icons">add</i> Deploy Template
+          <Button onClick={() => this.props.history.push("launch")}>
+            <i className="material-icons">add</i> Launch Template
           </Button>
           <SortFilterWrapper>
             <SortSelector

+ 12 - 6
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -36,14 +36,17 @@ type StateType = {
   action: ActionConfigType;
 };
 
+// TODO: put in shared, duped from LaunchTemplate.tsx
+const defaultActionConfig: ActionConfigType = {
+  git_repo: "",
+  image_repo_uri: "",
+  git_repo_id: 0,
+  dockerfile_path: "",
+};
+
 export default class SettingsSection extends Component<PropsType, StateType> {
   state = {
-    actionConfig: {
-      git_repo: "",
-      image_repo_uri: "",
-      git_repo_id: 0,
-      dockerfile_path: "",
-    } as ActionConfigType,
+    actionConfig: defaultActionConfig,
     sourceType: "",
     selectedImageUrl: "",
     selectedTag: "",
@@ -219,6 +222,9 @@ export default class SettingsSection extends Component<PropsType, StateType> {
           setActionConfig={(actionConfig: ActionConfigType) =>
             this.setState({ actionConfig })
           }
+          resetActionConfig={() =>
+            this.setState({ actionConfig: defaultActionConfig })
+          }
         />
       </>
     );

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

@@ -79,7 +79,7 @@ class Dashboard extends Component<PropsType, StateType> {
             <>
               <Banner>
                 <i className="material-icons">error_outline</i>
-                This project currently has no clusters conncted.
+                This project currently has no clusters connected.
               </Banner>
               <ProvisionerSettings infras={this.state.infras} />
             </>

+ 2 - 1
dashboard/src/main/home/integrations/Integrations.tsx

@@ -128,7 +128,8 @@ export default class Integrations extends Component<PropsType, StateType> {
               return (
                 <Credential key={i}>
                   <i className="material-icons">admin_panel_settings</i>{" "}
-                  {item.name}
+                  {/* TODO: handle different types of items (ie. registry vs repo) */}
+                  {item.name || item.repo_entity}
                 </Credential>
               );
             })}

+ 37 - 1
dashboard/src/main/home/launch/Launch.tsx

@@ -10,6 +10,7 @@ import ExpandedTemplate from "./expanded-template/ExpandedTemplate";
 import Loading from "components/Loading";
 
 import hardcodedNames from "./hardcodedNameDict";
+import { Link } from "react-router-dom";
 
 const tabOptions = [
   { label: "Launch service", value: "docker" },
@@ -44,7 +45,7 @@ export default class Templates extends Component<PropsType, StateType> {
           this.state.porterTemplates.sort((a, b) =>
             a.name === "docker" ? -1 : b.name === "docker" ? 1 : 0
           );
-          // TODO: properly find "docker" template instead of relying on first
+          // TODO: properly find "docker" template instead of relying on first entry
           this.setState({
             loading: false,
             currentTemplate: this.state.porterTemplates[0],
@@ -110,6 +111,25 @@ export default class Templates extends Component<PropsType, StateType> {
   };
 
   renderDefaultTemplate = () => {
+    if (!this.context.currentCluster) {
+      return (
+        <>
+          <Banner>
+            <i className="material-icons">error_outline</i>
+            <Link to="dashboard">Provision</Link> &nbsp;or&nbsp;
+            <Link
+              to="#"
+              onClick={() =>
+                this.context.setCurrentModal("ClusterInstructionsModal")
+              }
+            >
+              connect
+            </Link>
+            &nbsp;to a cluster
+          </Banner>
+        </>
+      );
+    }
     if (this.state.currentTemplate) {
       return (
         <ExpandedTemplate
@@ -186,6 +206,22 @@ const Placeholder = styled.div`
   }
 `;
 
+const Banner = styled.div`
+  height: 40px;
+  width: 100%;
+  margin: 30px 0 30px;
+  font-size: 13px;
+  display: flex;
+  border-radius: 5px;
+  padding-left: 15px;
+  align-items: center;
+  background: #ffffff11;
+  > i {
+    margin-right: 10px;
+    font-size: 18px;
+  }
+`;
+
 const LoadingWrapper = styled.div`
   padding-top: 300px;
 `;

+ 1 - 1
dashboard/src/main/home/launch/expanded-template/ExpandedTemplate.tsx

@@ -131,5 +131,5 @@ const LoadingWrapper = styled.div`
 const StyledExpandedTemplate = styled.div`
   width: calc(90% - 150px);
   min-width: 300px;
-  padding-top: 10px;
+  padding-top: 30px;
 `;

+ 73 - 81
dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx

@@ -50,6 +50,13 @@ type StateType = {
   pathIsSet: boolean;
 };
 
+const defaultActionConfig: ActionConfigType = {
+  git_repo: "",
+  image_repo_uri: "",
+  git_repo_id: 0,
+  dockerfile_path: "",
+};
+
 export default class LaunchTemplate extends Component<PropsType, StateType> {
   state = {
     currentView: "repo",
@@ -66,12 +73,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
     currentTab: null as string | null,
     tabContents: [] as any,
     namespaceOptions: [] as { label: string; value: string }[],
-    actionConfig: {
-      git_repo: "",
-      image_repo_uri: "",
-      git_repo_id: 0,
-      dockerfile_path: "",
-    } as ActionConfigType,
+    actionConfig: { ...defaultActionConfig },
     branch: "",
     pathIsSet: false,
   };
@@ -129,6 +131,8 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
         }
       )
       .then((_) => {
+        console.log("ST");
+        console.log(this.state.sourceType);
         if (this.state.sourceType === "repo") {
           this.createGHAction(name, this.state.selectedNamespace);
         }
@@ -212,7 +216,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
           version: "latest",
         }
       )
-      .then((res) => {
+      .then((_) => {
         if (this.state.sourceType === "repo") {
           this.createGHAction(name, this.state.selectedNamespace);
         }
@@ -220,20 +224,29 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
         this.setState({ saveValuesStatus: "successful" }, () => {
           // redirect to dashboard with namespace
         });
-        posthog.capture("Deployed template", {
-          name: this.props.currentTemplate.name,
-          namespace: this.state.selectedNamespace,
-          values: values,
-        });
+        try {
+          posthog.capture("Deployed template", {
+            name: this.props.currentTemplate.name,
+            namespace: this.state.selectedNamespace,
+            values: values,
+          });
+        } catch (error) {
+          console.log(error);
+        }
       })
       .catch((err) => {
         this.setState({ saveValuesStatus: "error" });
-        posthog.capture("Failed to deploy template", {
-          name: this.props.currentTemplate.name,
-          namespace: this.state.selectedNamespace,
-          values: values,
-          error: err,
-        });
+
+        try {
+          posthog.capture("Failed to deploy template", {
+            name: this.props.currentTemplate.name,
+            namespace: this.state.selectedNamespace,
+            values: values,
+            error: err,
+          });
+        } catch (error) {
+          console.log(error);
+        }
       });
   };
 
@@ -351,7 +364,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
     );
   };
 
-  renderTabRegion = () => {
+  renderSettingsRegion = () => {
     if (this.state.tabOptions.length > 0) {
       return (
         <>
@@ -392,9 +405,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
   };
 
   // Display if current template uses source (image or repo)
-  renderSourceSelector = () => {
-    let { currentProject } = this.context;
-
+  renderSourceSelectorContent = () => {
     if (this.props.form?.hasSource) {
       if (this.state.sourceType === "registry") {
         return (
@@ -422,30 +433,8 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
         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>
+              Select a repo to connect to, then a Dockerfile to build from.
+              <Required>*</Required>
             </Subtitle>
             <ActionConfEditor
               actionConfig={this.state.actionConfig}
@@ -460,6 +449,13 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
               }
               setBranch={(branch: string) => this.setState({ branch })}
               setPath={(pathIsSet: boolean) => this.setState({ pathIsSet })}
+              reset={() => {
+                this.setState({
+                  actionConfig: { ...defaultActionConfig },
+                  branch: "",
+                  pathIsSet: false,
+                });
+              }}
             />
             <br />
           </>
@@ -468,6 +464,25 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
     }
   };
 
+  renderSourceSelector = () => {
+    return (
+      <>
+        <TabRegion
+          options={[
+            { label: "Registry", value: "registry" },
+            { label: "Github", value: "repo" },
+          ]}
+          currentTab={this.state.sourceType}
+          setCurrentTab={(x) => this.setState({ sourceType: x })}
+        >
+          <StyledSourceBox>
+            {this.renderSourceSelectorContent()}
+          </StyledSourceBox>
+        </TabRegion>
+      </>
+    );
+  };
+
   render() {
     let { name, icon } = this.props.currentTemplate;
     let { currentTemplate } = this.props;
@@ -541,7 +556,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
           width="100%"
         />
         {this.renderSourceSelector()}
-        {this.renderTabRegion()}
+        {this.renderSettingsRegion()}
       </StyledLaunchTemplate>
     );
   }
@@ -563,13 +578,6 @@ const Link = styled.a`
   margin-left: 5px;
 `;
 
-const LineBreak = styled.div`
-  width: calc(100% - 0px);
-  height: 2px;
-  background: #ffffff20;
-  margin: 35px 0px 35px;
-`;
-
 const Wrapper = styled.div`
   width: 100%;
   position: relative;
@@ -654,7 +662,6 @@ const ClusterSection = styled.div`
   font-family: "Work Sans", sans-serif;
   font-size: 14px;
   font-weight: 500;
-  margin-top: 20px;
   margin-bottom: 15px;
 
   > i {
@@ -680,25 +687,6 @@ const Flex = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 11px;
-  border-radius: 2px;
-  color: #ffffff;
-`;
-
-const TitleSection = styled.div`
-  display: flex;
-  margin-left: -42px;
-  height: 40px;
-  flex-direction: row;
-  justify-content: space-between;
-  width: calc(100% + 42px);
-  align-items: center;
-`;
-
 const StyledLaunchTemplate = styled.div`
   width: 100%;
   padding-bottom: 150px;
@@ -713,11 +701,15 @@ const Highlight = styled.div`
     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" : ""};
+const StyledSourceBox = styled.div`
+  width: 100%;
+  height: 100%;
+  background: #ffffff11;
+  color: #ffffff;
+  padding: 10px 35px 25px;
+  position: relative;
+  border-radius: 5px;
+  font-size: 13px;
+  overflow: auto;
+  margin-bottom: 25px;
 `;

+ 1 - 1
dashboard/src/main/home/launch/expanded-template/TemplateInfo.tsx

@@ -297,7 +297,7 @@ const Title = styled.div`
 
 const TitleSection = styled.div`
   display: flex;
-  margin-left: -42px;
+  margin-left: 0px;
   flex-direction: row;
   height: 40px;
   justify-content: space-between;

+ 5 - 7
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -2,7 +2,7 @@ import React, { Component } from "react";
 import styled from "styled-components";
 import category from "assets/category.svg";
 import integrations from "assets/integrations.svg";
-import filter from "assets/filter.svg";
+import rocket from "assets/rocket.png";
 import settings from "assets/settings.svg";
 
 import { Context } from "shared/Context";
@@ -115,16 +115,14 @@ class Sidebar extends Component<PropsType, StateType> {
             onClick={() => this.props.history.push("launch")}
             selected={currentView === "launch"}
           >
-            <Img src={filter} />
+            <Img src={rocket} />
             Launch
           </NavButton>
           <NavButton
             selected={currentView === "integrations"}
-            /* 
-            onClick={() => {
-              setCurrentView('integrations')
-            }}
-            */
+            // onClick={() => {
+            //   this.props.history.push("integrations");
+            // }}
             onClick={() => {
               setCurrentModal("IntegrationsInstructionsModal", {});
             }}

+ 26 - 0
server/api/git_repo_handler.go

@@ -91,6 +91,32 @@ func (app *App) HandleListRepos(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(res)
 }
 
+// HandleDeleteProjectGitRepo handles the deletion of a Github Repo via the git repo ID
+func (app *App) HandleDeleteProjectGitRepo(w http.ResponseWriter, r *http.Request) {
+	id, err := strconv.ParseUint(chi.URLParam(r, "git_repo_id"), 0, 64)
+
+	if err != nil || id == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	repo, err := app.Repo.GitRepo.ReadGitRepo(uint(id))
+
+	if err != nil {
+		app.handleErrorRead(err, ErrProjectDataRead, w)
+		return
+	}
+
+	err = app.Repo.GitRepo.DeleteGitRepo(repo)
+
+	if err != nil {
+		app.handleErrorRead(err, ErrProjectDataRead, w)
+		return
+	}
+
+	w.WriteHeader(http.StatusOK)
+}
+
 // HandleGetBranches retrieves a list of branch names for a specified repo
 func (app *App) HandleGetBranches(w http.ResponseWriter, r *http.Request) {
 	tok, err := app.githubTokenFromRequest(r)

+ 14 - 0
server/router/router.go

@@ -939,6 +939,20 @@ func New(a *api.App) *chi.Mux {
 			),
 		)
 
+		r.Method(
+			"DELETE",
+			"/projects/{project_id}/gitrepos/{git_repo_id}",
+			auth.DoesUserHaveProjectAccess(
+				auth.DoesUserHaveGitRepoAccess(
+					requestlog.NewHandler(a.HandleDeleteProjectGitRepo, l),
+					mw.URLParam,
+					mw.URLParam,
+				),
+				mw.URLParam,
+				mw.WriteAccess,
+			),
+		)
+
 		r.Method(
 			"GET",
 			"/projects/{project_id}/gitrepos/{git_repo_id}/repos",