Explorar o código

Merge pull request #431 from porter-dev/beta.3.github-fixes

Github integration -- small improvements
abelanger5 %!s(int64=5) %!d(string=hai) anos
pai
achega
2346237aab

+ 1 - 1
dashboard/src/components/InfoTooltip.tsx

@@ -11,7 +11,7 @@ type StateType = {
 
 
 export default class InfoTooltip extends Component<PropsType, StateType> {
 export default class InfoTooltip extends Component<PropsType, StateType> {
   state = {
   state = {
-    showTooltip: false,
+    showTooltip: false
   };
   };
 
 
   render() {
   render() {

+ 1 - 1
dashboard/src/components/RadioSelector.tsx

@@ -53,7 +53,7 @@ const Indicator = styled.div<{ selected: boolean }>`
   height: 16px;
   height: 16px;
   border: 1px solid #ffffff55;
   border: 1px solid #ffffff55;
   margin: 1px 10px 0px 1px;
   margin: 1px 10px 0px 1px;
-  background: ${(props) => (props.selected ? "#ffffff22" : "#ffffff11")};
+  background: ${props => (props.selected ? "#ffffff22" : "#ffffff11")};
 `;
 `;
 
 
 const Circle = styled.div`
 const Circle = styled.div`

+ 2 - 2
dashboard/src/components/ResourceTab.tsx

@@ -26,7 +26,7 @@ type StateType = {
 export default class ResourceTab extends Component<PropsType, StateType> {
 export default class ResourceTab extends Component<PropsType, StateType> {
   state = {
   state = {
     expanded: this.props.expanded || false,
     expanded: this.props.expanded || false,
-    showTooltip: false,
+    showTooltip: false
   };
   };
 
 
   renderDropdownIcon = () => {
   renderDropdownIcon = () => {
@@ -95,7 +95,7 @@ export default class ResourceTab extends Component<PropsType, StateType> {
       handleClick,
       handleClick,
       selected,
       selected,
       status,
       status,
-      roundAllCorners,
+      roundAllCorners
     } = this.props;
     } = this.props;
     return (
     return (
       <StyledResourceTab
       <StyledResourceTab

+ 4 - 5
dashboard/src/components/SaveButton.tsx

@@ -132,15 +132,14 @@ const Button = styled.button`
   text-align: left;
   text-align: left;
   border: 0;
   border: 0;
   border-radius: 5px;
   border-radius: 5px;
-  background: ${(props) => (!props.disabled ? props.color : "#aaaabb")};
-  box-shadow: ${(props) =>
-    !props.disabled ? "0 2px 5px 0 #00000030" : "none"};
-  cursor: ${(props) => (!props.disabled ? "pointer" : "default")};
+  background: ${props => (!props.disabled ? props.color : "#aaaabb")};
+  box-shadow: ${props => (!props.disabled ? "0 2px 5px 0 #00000030" : "none")};
+  cursor: ${props => (!props.disabled ? "pointer" : "default")};
   user-select: none;
   user-select: none;
   :focus {
   :focus {
     outline: 0;
     outline: 0;
   }
   }
   :hover {
   :hover {
-    filter: ${(props) => (!props.disabled ? "brightness(120%)" : "")};
+    filter: ${props => (!props.disabled ? "brightness(120%)" : "")};
   }
   }
 `;
 `;

+ 2 - 2
dashboard/src/components/Selector.tsx

@@ -17,7 +17,7 @@ type StateType = {};
 
 
 export default class Selector extends Component<PropsType, StateType> {
 export default class Selector extends Component<PropsType, StateType> {
   state = {
   state = {
-    expanded: false,
+    expanded: false
   };
   };
 
 
   wrapperRef: any = React.createRef();
   wrapperRef: any = React.createRef();
@@ -192,7 +192,7 @@ const Dropdown = styled.div`
 
 
 const StyledSelector = styled.div<{ width: string }>`
 const StyledSelector = styled.div<{ width: string }>`
   position: relative;
   position: relative;
-  width: ${(props) => props.width};
+  width: ${props => props.width};
 `;
 `;
 
 
 const MainSelector = styled.div`
 const MainSelector = styled.div`

+ 1 - 1
dashboard/src/components/TabRegion.tsx

@@ -27,7 +27,7 @@ export default class TabRegion extends Component<PropsType, StateType> {
   componentDidUpdate(prevProps: PropsType) {
   componentDidUpdate(prevProps: PropsType) {
     let { options, currentTab } = this.props;
     let { options, currentTab } = this.props;
     if (prevProps.options !== options) {
     if (prevProps.options !== options) {
-      if (options.filter((x) => x.value === currentTab).length === 0) {
+      if (options.filter(x => x.value === currentTab).length === 0) {
         this.props.setCurrentTab(this.defaultTab());
         this.props.setCurrentTab(this.defaultTab());
       }
       }
     }
     }

+ 1 - 1
dashboard/src/components/TooltipParent.tsx

@@ -11,7 +11,7 @@ type StateType = {
 
 
 export default class TooltipParent extends Component<PropsType, StateType> {
 export default class TooltipParent extends Component<PropsType, StateType> {
   state = {
   state = {
-    showTooltip: false,
+    showTooltip: false
   };
   };
 
 
   renderTooltip = (): JSX.Element | undefined => {
   renderTooltip = (): JSX.Element | undefined => {

+ 122 - 48
dashboard/src/components/repo-selector/RepoList.tsx

@@ -20,13 +20,15 @@ type StateType = {
   repos: RepoType[];
   repos: RepoType[];
   loading: boolean;
   loading: boolean;
   error: boolean;
   error: boolean;
+  searchFilter: string;
 };
 };
 
 
-export default class ActionConfEditor extends Component<PropsType, StateType> {
+export default class RepoList extends Component<PropsType, StateType> {
   state = {
   state = {
     repos: [] as RepoType[],
     repos: [] as RepoType[],
     loading: true,
     loading: true,
     error: false,
     error: false,
+    searchFilter: "",
   };
   };
 
 
   // TODO: Try to unhook before unmount
   // TODO: Try to unhook before unmount
@@ -37,49 +39,75 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
     if (!this.props.userId && this.props.userId !== 0) {
     if (!this.props.userId && this.props.userId !== 0) {
       api
       api
         .getGitRepos("<token>", {}, { project_id: currentProject.id })
         .getGitRepos("<token>", {}, { project_id: currentProject.id })
-        .then((res) => {
+        .then(async (res) => {
+          if (res.data.length == 0) {
+            this.setState({ loading: false, error: false });
+            return
+          }
+
           var allRepos: any = [];
           var allRepos: any = [];
-          // TODO: make into promise.all
-          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 }
-              )
-              .then((res) => {
-                res.data.forEach((repo: any, id: number) => {
-                  repo.GHRepoID = grid;
-                });
-                allRepos = allRepos.concat(res.data);
-                allRepos.sort((a: any, b: any) => {
-                  if (a.FullName < b.FullName) {
-                    return -1;
-                  } else if (a.FullName > b.FullName) {
-                    return 1;
-                  } else {
-                    return 0;
-                  }
-                });
-                this.setState({
-                  repos: allRepos,
-                  loading: false,
-                  error: false,
+          var errors : any = [];
+
+          var promises = res.data.map((gitrepo: any, id: number) => {
+            return new Promise((resolve, reject) => {
+              api
+                .getGitRepoList(
+                  "<token>",
+                  {},
+                  { project_id: currentProject.id, git_repo_id: gitrepo.id }
+                )
+                .then((res) => {
+                  res.data.forEach((repo: any, id: number) => {
+                    repo.GHRepoID = gitrepo.id;
+                  });
+
+                  resolve(res.data)
+                })
+                .catch((err) => {
+                  errors.push(err)
+                  resolve([])
                 });
                 });
               })
               })
-              .catch((err) => {
-                console.log(err);
-                this.setState({ loading: false, error: true });
-              });
-          }
-          if (res.data.length < 1) {
-            this.setState({ loading: false, error: false });
+            })  
+
+          var sepRepos = await Promise.all(promises);
+
+          allRepos = [].concat.apply([], sepRepos);
+
+          // remove duplicates based on name
+          allRepos = allRepos.filter((repo : any, index : number, self : any) => {
+            var keep = index === self.findIndex((_repo : any) => {
+              return repo.FullName === _repo.FullName
+            })
+
+            return keep
+          })
+
+          // sort repos based on name
+          allRepos.sort((a: any, b: any) => {
+            if (a.FullName < b.FullName) {
+              return -1;
+            } else if (a.FullName > b.FullName) {
+              return 1;
+            } else {
+              return 0;
+            }
+          });
+
+          if (allRepos.length == 0 && errors.length > 0) {
+            this.setState({ loading: false, error: true });
+          } else {
+            this.setState({
+              repos: allRepos,
+              loading: false,
+              error: false,
+            });
           }
           }
         })
         })
         .catch((_) => this.setState({ loading: false, error: true }));
         .catch((_) => this.setState({ loading: false, error: true }));
     } else {
     } else {
       let grid = this.props.userId;
       let grid = this.props.userId;
+
       api
       api
         .getGitRepoList(
         .getGitRepoList(
           "<token>",
           "<token>",
@@ -87,14 +115,25 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
           { project_id: currentProject.id, git_repo_id: grid }
           { project_id: currentProject.id, git_repo_id: grid }
         )
         )
         .then((res) => {
         .then((res) => {
-          res.data.forEach((repo: any, id: number) => {
+          var repos : any = res.data
+
+          repos.forEach((repo: any, id: number) => {
             repo.GHRepoID = grid;
             repo.GHRepoID = grid;
           });
           });
-          // TODO: sort repos alphabetically
-          this.setState({ repos: res.data, loading: false, error: false });
+
+          repos.sort((a: any, b: any) => {
+            if (a.FullName < b.FullName) {
+              return -1;
+            } else if (a.FullName > b.FullName) {
+              return 1;
+            } else {
+              return 0;
+            }
+          });
+
+          this.setState({ repos: repos, loading: false, error: false });
         })
         })
         .catch((err) => {
         .catch((err) => {
-          console.log(err);
           this.setState({ loading: false, error: true });
           this.setState({ loading: false, error: true });
         });
         });
     }
     }
@@ -132,7 +171,9 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
       );
       );
     }
     }
 
 
-    return repos.map((repo: RepoType, i: number) => {
+    return repos.filter((repo: RepoType, i: number) => {
+      return repo.FullName.includes(this.state.searchFilter || "")
+    }).map((repo: RepoType, i: number) => {
       return (
       return (
         <RepoName
         <RepoName
           key={i}
           key={i}
@@ -150,7 +191,7 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
 
 
   renderExpanded = () => {
   renderExpanded = () => {
     if (this.props.readOnly) {
     if (this.props.readOnly) {
-      return <ExpandedWrapperAlt>{this.renderRepoList()}</ExpandedWrapperAlt>;
+      return <ExpandedWrapperAlt>{this.renderRepoList()}</ExpandedWrapperAlt>
     } else {
     } else {
       return (
       return (
         <ExpandedWrapper>
         <ExpandedWrapper>
@@ -159,10 +200,18 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
             lastItem={false}
             lastItem={false}
             readOnly={this.props.readOnly}
             readOnly={this.props.readOnly}
           >
           >
-            <img src={info} />
-            Select Repo
+            <i className="material-icons">search</i>
+            <SearchInput 
+              value={this.state.searchFilter}
+              onChange={(e: any) => {
+                this.setState({ searchFilter: e.target.value });
+              }}
+              placeholder="Search repos..."
+            />
           </InfoRow>
           </InfoRow>
-          {this.renderRepoList()}
+          <ExpandedWrapper>
+            {this.renderRepoList()}
+          </ExpandedWrapper>
         </ExpandedWrapper>
         </ExpandedWrapper>
       );
       );
     }
     }
@@ -173,7 +222,7 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
   }
   }
 }
 }
 
 
-ActionConfEditor.contextType = Context;
+RepoList.contextType = Context;
 
 
 const RepoName = styled.div`
 const RepoName = styled.div`
   display: flex;
   display: flex;
@@ -209,11 +258,12 @@ const RepoName = styled.div`
     }
     }
   }
   }
 
 
-  > img {
+  > img,i {
     width: 18px;
     width: 18px;
     height: 18px;
     height: 18px;
     margin-left: 12px;
     margin-left: 12px;
     margin-right: 12px;
     margin-right: 12px;
+    font-size: 20px;
   }
   }
 `;
 `;
 
 
@@ -222,6 +272,10 @@ const InfoRow = styled(RepoName)`
   color: #ffffff55;
   color: #ffffff55;
   :hover {
   :hover {
     background: #ffffff11;
     background: #ffffff11;
+
+    > i {
+      background: none;
+    }
   }
   }
 `;
 `;
 
 
@@ -239,7 +293,16 @@ const ExpandedWrapper = styled.div`
   width: 100%;
   width: 100%;
   border-radius: 3px;
   border-radius: 3px;
   border: 0px solid #ffffff44;
   border: 0px solid #ffffff44;
-  max-height: 275px;
+  max-height: 235px;
+  top: 40px; 
+
+  > i {
+    font-size: 18px;
+    display: block;
+    position: absolute; 
+    left: 10px; 
+    top: 10px; 
+  }
 `;
 `;
 
 
 const ExpandedWrapperAlt = styled(ExpandedWrapper)`
 const ExpandedWrapperAlt = styled(ExpandedWrapper)`
@@ -254,3 +317,14 @@ const A = styled.a`
   margin-left: 5px;
   margin-left: 5px;
   cursor: pointer;
   cursor: pointer;
 `;
 `;
+
+const SearchInput = styled.input`
+  outline: none;
+  border: none;
+  font-size: 13px;
+  background: none;
+  width: 100%;
+  color: white;
+  padding: 0;
+  height: 20px; 
+`;

+ 2 - 2
dashboard/src/main/CurrentError.tsx

@@ -12,7 +12,7 @@ type StateType = {};
 
 
 export default class CurrentError extends Component<PropsType, StateType> {
 export default class CurrentError extends Component<PropsType, StateType> {
   state = {
   state = {
-    expanded: false,
+    expanded: false
   };
   };
 
 
   componentDidUpdate(prevProps: PropsType) {
   componentDidUpdate(prevProps: PropsType) {
@@ -32,7 +32,7 @@ export default class CurrentError extends Component<PropsType, StateType> {
           <StyledCurrentError onClick={() => this.setState({ expanded: true })}>
           <StyledCurrentError onClick={() => this.setState({ expanded: true })}>
             <ErrorText>Error: {this.props.currentError}</ErrorText>
             <ErrorText>Error: {this.props.currentError}</ErrorText>
             <CloseButton
             <CloseButton
-              onClick={(e) => {
+              onClick={e => {
                 this.context.setCurrentError(null);
                 this.context.setCurrentError(null);
                 e.stopPropagation();
                 e.stopPropagation();
               }}
               }}

+ 9 - 11
dashboard/src/main/Main.tsx

@@ -29,7 +29,7 @@ export default class Main extends Component<PropsType, StateType> {
     loading: true,
     loading: true,
     isLoggedIn: false,
     isLoggedIn: false,
     isEmailVerified: false,
     isEmailVerified: false,
-    initialized: localStorage.getItem("init") === "true",
+    initialized: localStorage.getItem("init") === "true"
   };
   };
 
 
   componentDidMount() {
   componentDidMount() {
@@ -39,20 +39,20 @@ export default class Main extends Component<PropsType, StateType> {
     error && setCurrentError(error);
     error && setCurrentError(error);
     api
     api
       .checkAuth("", {}, {})
       .checkAuth("", {}, {})
-      .then((res) => {
+      .then(res => {
         if (res && res.data) {
         if (res && res.data) {
           setUser(res?.data?.id, res?.data?.email);
           setUser(res?.data?.id, res?.data?.email);
           this.setState({
           this.setState({
             isLoggedIn: true,
             isLoggedIn: true,
             isEmailVerified: res?.data?.email_verified,
             isEmailVerified: res?.data?.email_verified,
             initialized: true,
             initialized: true,
-            loading: false,
+            loading: false
           });
           });
         } else {
         } else {
           this.setState({ isLoggedIn: false, loading: false });
           this.setState({ isLoggedIn: false, loading: false });
         }
         }
       })
       })
-      .catch((err) => this.setState({ isLoggedIn: false, loading: false }));
+      .catch(err => this.setState({ isLoggedIn: false, loading: false }));
   }
   }
 
 
   initialize = () => {
   initialize = () => {
@@ -63,20 +63,20 @@ export default class Main extends Component<PropsType, StateType> {
   authenticate = () => {
   authenticate = () => {
     api
     api
       .checkAuth("", {}, {})
       .checkAuth("", {}, {})
-      .then((res) => {
+      .then(res => {
         if (res && res.data) {
         if (res && res.data) {
           this.context.setUser(res?.data?.id, res?.data?.email);
           this.context.setUser(res?.data?.id, res?.data?.email);
           this.setState({
           this.setState({
             isLoggedIn: true,
             isLoggedIn: true,
             isEmailVerified: res?.data?.email_verified,
             isEmailVerified: res?.data?.email_verified,
             initialized: true,
             initialized: true,
-            loading: false,
+            loading: false
           });
           });
         } else {
         } else {
           this.setState({ isLoggedIn: false, loading: false });
           this.setState({ isLoggedIn: false, loading: false });
         }
         }
       })
       })
-      .catch((err) => this.setState({ isLoggedIn: false, loading: false }));
+      .catch(err => this.setState({ isLoggedIn: false, loading: false }));
   };
   };
 
 
   handleLogOut = () => {
   handleLogOut = () => {
@@ -89,9 +89,7 @@ export default class Main extends Component<PropsType, StateType> {
         this.setState({ isLoggedIn: false, initialized: true });
         this.setState({ isLoggedIn: false, initialized: true });
         localStorage.clear();
         localStorage.clear();
       })
       })
-      .catch((err) =>
-        this.context.setCurrentError(err.response.data.errors[0])
-      );
+      .catch(err => this.context.setCurrentError(err.response.data.errors[0]));
   };
   };
 
 
   renderMain = () => {
   renderMain = () => {
@@ -168,7 +166,7 @@ export default class Main extends Component<PropsType, StateType> {
         />
         />
         <Route
         <Route
           path={`/:baseRoute`}
           path={`/:baseRoute`}
-          render={(routeProps) => {
+          render={routeProps => {
             const baseRoute = routeProps.match.params.baseRoute;
             const baseRoute = routeProps.match.params.baseRoute;
             if (
             if (
               this.state.isLoggedIn &&
               this.state.isLoggedIn &&

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

@@ -159,6 +159,7 @@ class IntegrationCategories extends Component<PropsType, StateType> {
             integrations={this.state.currentOptions}
             integrations={this.state.currentOptions}
             titles={this.state.currentTitles}
             titles={this.state.currentTitles}
             itemIdentifier={this.state.currentIntegrationData}
             itemIdentifier={this.state.currentIntegrationData}
+            updateIntegrationList={() => this.getIntegrationsForCategory(this.props.category)}
           />
           />
         </div>
         </div>
       );
       );
@@ -195,6 +196,7 @@ class IntegrationCategories extends Component<PropsType, StateType> {
             integrations={this.state.currentOptions}
             integrations={this.state.currentOptions}
             titles={this.state.currentTitles}
             titles={this.state.currentTitles}
             itemIdentifier={this.state.currentIds}
             itemIdentifier={this.state.currentIds}
+            updateIntegrationList={() => this.getIntegrationsForCategory(this.props.category)}
           />
           />
         </div>
         </div>
       );
       );

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

@@ -4,6 +4,8 @@ import styled from "styled-components";
 import { Context } from "shared/Context";
 import { Context } from "shared/Context";
 import { integrationList } from "shared/common";
 import { integrationList } from "shared/common";
 import IntegrationRow from "./IntegrationRow";
 import IntegrationRow from "./IntegrationRow";
+import ConfirmOverlay from "components/ConfirmOverlay";
+import api from "shared/api";
 
 
 type PropsType = {
 type PropsType = {
   setCurrent?: (x: string) => void;
   setCurrent?: (x: string) => void;
@@ -12,15 +14,22 @@ type PropsType = {
   itemIdentifier?: any[];
   itemIdentifier?: any[];
   titles?: string[];
   titles?: string[];
   isCategory?: boolean;
   isCategory?: boolean;
+  updateIntegrationList: () => void;
 };
 };
 
 
 type StateType = {
 type StateType = {
   displayExpanded: boolean[];
   displayExpanded: boolean[];
+  isDelete: boolean;
+  deleteName: string;
+  deleteID: number;
 };
 };
 
 
 export default class IntegrationList extends Component<PropsType, StateType> {
 export default class IntegrationList extends Component<PropsType, StateType> {
   state = {
   state = {
     displayExpanded: this.props.integrations.map(() => false),
     displayExpanded: this.props.integrations.map(() => false),
+    isDelete: false,
+    deleteName: "",
+    deleteID: 0,
   };
   };
 
 
   allCollapsed = () =>
   allCollapsed = () =>
@@ -51,14 +60,59 @@ export default class IntegrationList extends Component<PropsType, StateType> {
     this.setState({ displayExpanded: x });
     this.setState({ displayExpanded: x });
   };
   };
 
 
+  triggerDelete = (event: MouseEvent, i: number, id: number) => {
+    if (event) {
+      event.stopPropagation();
+    }
+
+    this.setState({ isDelete: true, deleteName: this.props.titles[i], deleteID: id })
+  }
+
+  handleDeleteIntegration = () => {
+    let { currentProject } = this.context;
+
+    if (this.props.currentCategory === "registry") {
+      api.deleteRegistryIntegration(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          registry_id: this.state.deleteID,
+        }
+      ).then(() => {
+        this.setState({ isDelete: false })
+        this.props.updateIntegrationList()
+      }).catch((err) => {
+        this.context.setCurrentError(err)
+      })
+    } else if (this.props.currentCategory === "repo") {
+      api.deleteGitRepoIntegration(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          git_repo_id: this.state.deleteID,
+        }
+      ).then(() => {
+        this.setState({ isDelete: false })
+        this.props.updateIntegrationList()
+      }).catch((err) => {
+        this.context.setCurrentError(err)
+      })
+    }
+  }
+
   handleParent = (event: any, integration: string) =>
   handleParent = (event: any, integration: string) =>
     this.props.setCurrent && this.props.setCurrent(integration);
     this.props.setCurrent && this.props.setCurrent(integration);
 
 
   renderContents = () => {
   renderContents = () => {
     let { integrations, titles, setCurrent, isCategory } = this.props;
     let { integrations, titles, setCurrent, isCategory } = this.props;
+
     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 label = titles[i];
         let label = titles[i];
+        let item_id = this.props.itemIdentifier[i].id || this.props.itemIdentifier[i]
+
         return (
         return (
           <IntegrationRow
           <IntegrationRow
             category={this.props.currentCategory}
             category={this.props.currentCategory}
@@ -68,6 +122,7 @@ export default class IntegrationList extends Component<PropsType, StateType> {
             itemId={this.props.itemIdentifier[i]}
             itemId={this.props.itemIdentifier[i]}
             label={label}
             label={label}
             toggleCollapse={(e: MouseEvent) => this.toggleDisplay(e, i)}
             toggleCollapse={(e: MouseEvent) => this.toggleDisplay(e, i)}
+            triggerDelete={(e: MouseEvent) => this.triggerDelete(e, i, item_id)}
           ></IntegrationRow>
           ></IntegrationRow>
         );
         );
       });
       });
@@ -123,6 +178,12 @@ export default class IntegrationList extends Component<PropsType, StateType> {
   render() {
   render() {
     return (
     return (
       <StyledIntegrationList>
       <StyledIntegrationList>
+        <ConfirmOverlay
+          show={this.state.isDelete}
+          message={`Are you sure you want to delete the ${this.props.currentCategory === "registry" ? "Docker registry integration" : "Github integration"} with name ${this.state.deleteName}?`}
+          onYes={this.handleDeleteIntegration}
+          onNo={() => this.setState({ isDelete: false })}
+        />
         {this.props.titles && this.props.titles.length > 0 && (
         {this.props.titles && this.props.titles.length > 0 && (
           <ControlRow>{this.collapseAllButton()}</ControlRow>
           <ControlRow>{this.collapseAllButton()}</ControlRow>
         )}
         )}

+ 9 - 2
dashboard/src/main/home/integrations/IntegrationRow.tsx

@@ -10,6 +10,7 @@ import CreateIntegrationForm from "./create-integration/CreateIntegrationForm";
 
 
 type PropsType = {
 type PropsType = {
   toggleCollapse: MouseEventHandler;
   toggleCollapse: MouseEventHandler;
+  triggerDelete: MouseEventHandler;
   label: string;
   label: string;
   integration: string;
   integration: string;
   expanded: boolean;
   expanded: boolean;
@@ -63,6 +64,12 @@ export default class IntegrationRow extends Component<PropsType, StateType> {
           <MaterialIconTray disabled={false}>
           <MaterialIconTray disabled={false}>
             {/* <i className="material-icons"
             {/* <i className="material-icons"
             onClick={this.editButtonOnClick}>mode_edit</i> */}
             onClick={this.editButtonOnClick}>mode_edit</i> */}
+            <i
+              className="material-icons"
+              onClick={this.props.triggerDelete}
+            >
+              delete
+            </i>
             <I
             <I
               className="material-icons"
               className="material-icons"
               showList={this.props.expanded}
               showList={this.props.expanded}
@@ -168,8 +175,7 @@ const MainRow = styled.div`
 `;
 `;
 
 
 const MaterialIconTray = styled.div`
 const MaterialIconTray = styled.div`
-  width: 32px;
-  margin-right: -7px;
+  max-width: 60px;
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   justify-content: space-between;
   justify-content: space-between;
@@ -178,6 +184,7 @@ const MaterialIconTray = styled.div`
     border-radius: 20px;
     border-radius: 20px;
     font-size: 18px;
     font-size: 18px;
     padding: 5px;
     padding: 5px;
+    margin: 0 5px; 
     color: #ffffff44;
     color: #ffffff44;
     :hover {
     :hover {
       background: ${(props: { disabled: boolean }) =>
       background: ${(props: { disabled: boolean }) =>

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

@@ -85,6 +85,7 @@ class Integrations extends Component<PropsType, StateType> {
               integrations={["kubernetes", "registry", "repo"]}
               integrations={["kubernetes", "registry", "repo"]}
               setCurrent={(x) => this.props.history.push(`/integrations/${x}`)}
               setCurrent={(x) => this.props.history.push(`/integrations/${x}`)}
               isCategory={true}
               isCategory={true}
+              updateIntegrationList={() => {}}
             />
             />
           </div>
           </div>
         </Route>
         </Route>

+ 2 - 2
dashboard/src/shared/Context.tsx

@@ -72,9 +72,9 @@ class ContextProvider extends Component {
         currentProject: null,
         currentProject: null,
         projects: [],
         projects: [],
         user: null,
         user: null,
-        devOpsMode: true,
+        devOpsMode: true
       });
       });
-    },
+    }
   };
   };
 
 
   render() {
   render() {

+ 3 - 3
dashboard/src/shared/ansiparser.tsx

@@ -8,7 +8,7 @@ const foregroundColors = {
   "35": "magenta",
   "35": "magenta",
   "36": "cyan",
   "36": "cyan",
   "37": "white",
   "37": "white",
-  "90": "grey",
+  "90": "grey"
 } as Record<string, string>;
 } as Record<string, string>;
 
 
 const backgroundColors = {
 const backgroundColors = {
@@ -19,13 +19,13 @@ const backgroundColors = {
   "44": "blue",
   "44": "blue",
   "45": "magenta",
   "45": "magenta",
   "46": "cyan",
   "46": "cyan",
-  "47": "white",
+  "47": "white"
 } as Record<string, string>;
 } as Record<string, string>;
 
 
 const styles = {
 const styles = {
   "1": "bold",
   "1": "bold",
   "3": "italic",
   "3": "italic",
-  "4": "underline",
+  "4": "underline"
 } as Record<string, string>;
 } as Record<string, string>;
 
 
 const eraseChar = (matchingText: any, result: any) => {
 const eraseChar = (matchingText: any, result: any) => {

+ 84 - 65
dashboard/src/shared/api.tsx

@@ -18,7 +18,7 @@ const connectECRRegistry = baseApi<
     aws_integration_id: string;
     aws_integration_id: string;
   },
   },
   { id: number }
   { id: number }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.id}/registries`;
   return `/api/projects/${pathParams.id}/registries`;
 });
 });
 
 
@@ -29,7 +29,7 @@ const connectGCRRegistry = baseApi<
     url: string;
     url: string;
   },
   },
   { id: number }
   { id: number }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.id}/registries`;
   return `/api/projects/${pathParams.id}/registries`;
 });
 });
 
 
@@ -41,7 +41,7 @@ const createAWSIntegration = baseApi<
     aws_secret_access_key: string;
     aws_secret_access_key: string;
   },
   },
   { id: number }
   { id: number }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.id}/integrations/aws`;
   return `/api/projects/${pathParams.id}/integrations/aws`;
 });
 });
 
 
@@ -54,7 +54,7 @@ const createDOCR = baseApi<
   {
   {
     project_id: number;
     project_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.project_id}/provision/docr`;
   return `/api/projects/${pathParams.project_id}/provision/docr`;
 });
 });
 
 
@@ -67,11 +67,11 @@ const createDOKS = baseApi<
   {
   {
     project_id: number;
     project_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.project_id}/provision/doks`;
   return `/api/projects/${pathParams.project_id}/provision/doks`;
 });
 });
 
 
-const createEmailVerification = baseApi<{}, {}>("POST", (pathParams) => {
+const createEmailVerification = baseApi<{}, {}>("POST", pathParams => {
   return `/api/email/verify/initiate`;
   return `/api/email/verify/initiate`;
 });
 });
 
 
@@ -84,7 +84,7 @@ const createGCPIntegration = baseApi<
   {
   {
     project_id: number;
     project_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.project_id}/integrations/gcp`;
   return `/api/projects/${pathParams.project_id}/integrations/gcp`;
 });
 });
 
 
@@ -95,7 +95,7 @@ const createGCR = baseApi<
   {
   {
     project_id: number;
     project_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.project_id}/provision/gcr`;
   return `/api/projects/${pathParams.project_id}/provision/gcr`;
 });
 });
 
 
@@ -115,7 +115,7 @@ const createGHAction = baseApi<
     RELEASE_NAME: string;
     RELEASE_NAME: string;
     RELEASE_NAMESPACE: string;
     RELEASE_NAMESPACE: string;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   let { project_id, CLUSTER_ID, RELEASE_NAME, RELEASE_NAMESPACE } = pathParams;
   let { project_id, CLUSTER_ID, RELEASE_NAME, RELEASE_NAMESPACE } = pathParams;
   return `/api/projects/${project_id}/ci/actions?cluster_id=${CLUSTER_ID}&name=${RELEASE_NAME}&namespace=${RELEASE_NAMESPACE}`;
   return `/api/projects/${project_id}/ci/actions?cluster_id=${CLUSTER_ID}&name=${RELEASE_NAME}&namespace=${RELEASE_NAMESPACE}`;
 });
 });
@@ -128,7 +128,7 @@ const createGKE = baseApi<
   {
   {
     project_id: number;
     project_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.project_id}/provision/gke`;
   return `/api/projects/${pathParams.project_id}/provision/gke`;
 });
 });
 
 
@@ -139,7 +139,7 @@ const createInvite = baseApi<
   {
   {
     id: number;
     id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.id}/invites`;
   return `/api/projects/${pathParams.id}/invites`;
 });
 });
 
 
@@ -148,7 +148,7 @@ const createPasswordReset = baseApi<
     email: string;
     email: string;
   },
   },
   {}
   {}
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/password/reset/initiate`;
   return `/api/password/reset/initiate`;
 });
 });
 
 
@@ -159,7 +159,7 @@ const createPasswordResetVerify = baseApi<
     token_id: number;
     token_id: number;
   },
   },
   {}
   {}
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/password/reset/verify`;
   return `/api/password/reset/verify`;
 });
 });
 
 
@@ -171,11 +171,11 @@ const createPasswordResetFinalize = baseApi<
     new_password: string;
     new_password: string;
   },
   },
   {}
   {}
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/password/reset/finalize`;
   return `/api/password/reset/finalize`;
 });
 });
 
 
-const createProject = baseApi<{ name: string }, {}>("POST", (pathParams) => {
+const createProject = baseApi<{ name: string }, {}>("POST", pathParams => {
   return `/api/projects`;
   return `/api/projects`;
 });
 });
 
 
@@ -187,7 +187,7 @@ const createSubdomain = baseApi<
     id: number;
     id: number;
     cluster_id: number;
     cluster_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   let { cluster_id, id } = pathParams;
   let { cluster_id, id } = pathParams;
 
 
   return `/api/projects/${id}/k8s/subdomain?cluster_id=${cluster_id}`;
   return `/api/projects/${id}/k8s/subdomain?cluster_id=${cluster_id}`;
@@ -199,21 +199,41 @@ const deleteCluster = baseApi<
     project_id: number;
     project_id: number;
     cluster_id: number;
     cluster_id: number;
   }
   }
->("DELETE", (pathParams) => {
+>("DELETE", pathParams => {
   return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}`;
   return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}`;
 });
 });
 
 
+const deleteGitRepoIntegration = baseApi<
+  {},
+  {
+    project_id: number;
+    git_repo_id: number;
+  }
+>("DELETE", pathParams => {
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id}`;
+});
+
 const deleteInvite = baseApi<{}, { id: number; invId: number }>(
 const deleteInvite = baseApi<{}, { id: number; invId: number }>(
   "DELETE",
   "DELETE",
-  (pathParams) => {
+  pathParams => {
     return `/api/projects/${pathParams.id}/invites/${pathParams.invId}`;
     return `/api/projects/${pathParams.id}/invites/${pathParams.invId}`;
   }
   }
 );
 );
 
 
-const deleteProject = baseApi<{}, { id: number }>("DELETE", (pathParams) => {
+const deleteProject = baseApi<{}, { id: number }>("DELETE", pathParams => {
   return `/api/projects/${pathParams.id}`;
   return `/api/projects/${pathParams.id}`;
 });
 });
 
 
+const deleteRegistryIntegration = baseApi<
+  {},
+  {
+    project_id: number;
+    registry_id: number;
+  }
+>("DELETE", pathParams => {
+  return `/api/projects/${pathParams.project_id}/registries/${pathParams.registry_id}`;
+});
+
 const deployTemplate = baseApi<
 const deployTemplate = baseApi<
   {
   {
     templateName: string;
     templateName: string;
@@ -230,7 +250,7 @@ const deployTemplate = baseApi<
     version: string;
     version: string;
     repo_url?: string;
     repo_url?: string;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   let { cluster_id, id, name, version, repo_url } = pathParams;
   let { cluster_id, id, name, version, repo_url } = pathParams;
 
 
   if (repo_url) {
   if (repo_url) {
@@ -247,7 +267,7 @@ const destroyCluster = baseApi<
     project_id: number;
     project_id: number;
     infra_id: number;
     infra_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/eks/destroy`;
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/eks/destroy`;
 });
 });
 
 
@@ -263,7 +283,7 @@ const getBranchContents = baseApi<
     name: string;
     name: string;
     branch: string;
     branch: string;
   }
   }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id}/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name}/${pathParams.branch}/contents`;
   return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id}/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name}/${pathParams.branch}/contents`;
 });
 });
 
 
@@ -276,7 +296,7 @@ const getBranches = baseApi<
     owner: string;
     owner: string;
     name: string;
     name: string;
   }
   }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id}/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name}/branches`;
   return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id}/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name}/branches`;
 });
 });
 
 
@@ -287,7 +307,7 @@ const getChart = baseApi<
     storage: StorageType;
     storage: StorageType;
   },
   },
   { id: number; name: string; revision: number }
   { id: number; name: string; revision: number }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}`;
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}`;
 });
 });
 
 
@@ -302,7 +322,7 @@ const getCharts = baseApi<
     statusFilter: string[];
     statusFilter: string[];
   },
   },
   { id: number }
   { id: number }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/releases`;
   return `/api/projects/${pathParams.id}/releases`;
 });
 });
 
 
@@ -313,7 +333,7 @@ const getChartComponents = baseApi<
     storage: StorageType;
     storage: StorageType;
   },
   },
   { id: number; name: string; revision: number }
   { id: number; name: string; revision: number }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}/components`;
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}/components`;
 });
 });
 
 
@@ -324,13 +344,13 @@ const getChartControllers = baseApi<
     storage: StorageType;
     storage: StorageType;
   },
   },
   { id: number; name: string; revision: number }
   { id: number; name: string; revision: number }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}/controllers`;
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}/controllers`;
 });
 });
 
 
 const getClusterIntegrations = baseApi("GET", "/api/integrations/cluster");
 const getClusterIntegrations = baseApi("GET", "/api/integrations/cluster");
 
 
-const getClusters = baseApi<{}, { id: number }>("GET", (pathParams) => {
+const getClusters = baseApi<{}, { id: number }>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/clusters`;
   return `/api/projects/${pathParams.id}/clusters`;
 });
 });
 
 
@@ -340,7 +360,7 @@ const getGitRepoList = baseApi<
     project_id: number;
     project_id: number;
     git_repo_id: number;
     git_repo_id: number;
   }
   }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id}/repos`;
   return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id}/repos`;
 });
 });
 
 
@@ -349,7 +369,7 @@ const getGitRepos = baseApi<
   {
   {
     project_id: number;
     project_id: number;
   }
   }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.project_id}/gitrepos`;
   return `/api/projects/${pathParams.project_id}/gitrepos`;
 });
 });
 
 
@@ -359,7 +379,7 @@ const getImageRepos = baseApi<
     project_id: number;
     project_id: number;
     registry_id: number;
     registry_id: number;
   }
   }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.project_id}/registries/${pathParams.registry_id}/repositories`;
   return `/api/projects/${pathParams.project_id}/registries/${pathParams.registry_id}/repositories`;
 });
 });
 
 
@@ -370,7 +390,7 @@ const getImageTags = baseApi<
     registry_id: number;
     registry_id: number;
     repo_name: string;
     repo_name: string;
   }
   }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.project_id}/registries/${pathParams.registry_id}/repositories/${pathParams.repo_name}`;
   return `/api/projects/${pathParams.project_id}/registries/${pathParams.registry_id}/repositories/${pathParams.repo_name}`;
 });
 });
 
 
@@ -379,7 +399,7 @@ const getInfra = baseApi<
   {
   {
     project_id: number;
     project_id: number;
   }
   }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.project_id}/infra`;
   return `/api/projects/${pathParams.project_id}/infra`;
 });
 });
 
 
@@ -388,11 +408,11 @@ const getIngress = baseApi<
     cluster_id: number;
     cluster_id: number;
   },
   },
   { name: string; namespace: string; id: number }
   { name: string; namespace: string; id: number }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/k8s/${pathParams.namespace}/ingress/${pathParams.name}`;
   return `/api/projects/${pathParams.id}/k8s/${pathParams.namespace}/ingress/${pathParams.name}`;
 });
 });
 
 
-const getInvites = baseApi<{}, { id: number }>("GET", (pathParams) => {
+const getInvites = baseApi<{}, { id: number }>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/invites`;
   return `/api/projects/${pathParams.id}/invites`;
 });
 });
 
 
@@ -402,7 +422,7 @@ const getMatchingPods = baseApi<
     selectors: string[];
     selectors: string[];
   },
   },
   { id: number }
   { id: number }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/k8s/pods`;
   return `/api/projects/${pathParams.id}/k8s/pods`;
 });
 });
 
 
@@ -418,7 +438,7 @@ const getMetrics = baseApi<
     resolution: string;
     resolution: string;
   },
   },
   { id: number }
   { id: number }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/k8s/metrics`;
   return `/api/projects/${pathParams.id}/k8s/metrics`;
 });
 });
 
 
@@ -427,7 +447,7 @@ const getNamespaces = baseApi<
     cluster_id: number;
     cluster_id: number;
   },
   },
   { id: number }
   { id: number }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/k8s/namespaces`;
   return `/api/projects/${pathParams.id}/k8s/namespaces`;
 });
 });
 
 
@@ -436,26 +456,23 @@ const getOAuthIds = baseApi<
   {
   {
     project_id: number;
     project_id: number;
   }
   }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.project_id}/integrations/oauth`;
   return `/api/projects/${pathParams.project_id}/integrations/oauth`;
 });
 });
 
 
-const getProjectClusters = baseApi<{}, { id: number }>("GET", (pathParams) => {
+const getProjectClusters = baseApi<{}, { id: number }>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/clusters`;
   return `/api/projects/${pathParams.id}/clusters`;
 });
 });
 
 
-const getProjectRegistries = baseApi<{}, { id: number }>(
-  "GET",
-  (pathParams) => {
-    return `/api/projects/${pathParams.id}/registries`;
-  }
-);
+const getProjectRegistries = baseApi<{}, { id: number }>("GET", pathParams => {
+  return `/api/projects/${pathParams.id}/registries`;
+});
 
 
-const getProjectRepos = baseApi<{}, { id: number }>("GET", (pathParams) => {
+const getProjectRepos = baseApi<{}, { id: number }>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/repos`;
   return `/api/projects/${pathParams.id}/repos`;
 });
 });
 
 
-const getProjects = baseApi<{}, { id: number }>("GET", (pathParams) => {
+const getProjects = baseApi<{}, { id: number }>("GET", pathParams => {
   return `/api/users/${pathParams.id}/projects`;
   return `/api/users/${pathParams.id}/projects`;
 });
 });
 
 
@@ -464,7 +481,7 @@ const getPrometheusIsInstalled = baseApi<
     cluster_id: number;
     cluster_id: number;
   },
   },
   { id: number }
   { id: number }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/k8s/prometheus/detect`;
   return `/api/projects/${pathParams.id}/k8s/prometheus/detect`;
 });
 });
 
 
@@ -477,7 +494,7 @@ const getReleaseToken = baseApi<
     storage: StorageType;
     storage: StorageType;
   },
   },
   { name: string; id: number }
   { name: string; id: number }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/webhook_token`;
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/webhook_token`;
 });
 });
 
 
@@ -489,7 +506,7 @@ const destroyEKS = baseApi<
     project_id: number;
     project_id: number;
     infra_id: number;
     infra_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/eks/destroy`;
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/eks/destroy`;
 });
 });
 
 
@@ -501,7 +518,7 @@ const destroyGKE = baseApi<
     project_id: number;
     project_id: number;
     infra_id: number;
     infra_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/gke/destroy`;
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/gke/destroy`;
 });
 });
 
 
@@ -513,13 +530,13 @@ const destroyDOKS = baseApi<
     project_id: number;
     project_id: number;
     infra_id: number;
     infra_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/doks/destroy`;
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/doks/destroy`;
 });
 });
 
 
 const getRepoIntegrations = baseApi("GET", "/api/integrations/repo");
 const getRepoIntegrations = baseApi("GET", "/api/integrations/repo");
 
 
-const getRepos = baseApi<{}, { id: number }>("GET", (pathParams) => {
+const getRepos = baseApi<{}, { id: number }>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/repos`;
   return `/api/projects/${pathParams.id}/repos`;
 });
 });
 
 
@@ -530,7 +547,7 @@ const getRevisions = baseApi<
     storage: StorageType;
     storage: StorageType;
   },
   },
   { id: number; name: string }
   { id: number; name: string }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/history`;
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/history`;
 });
 });
 
 
@@ -539,7 +556,7 @@ const getTemplateInfo = baseApi<
     repo_url?: string;
     repo_url?: string;
   },
   },
   { name: string; version: string }
   { name: string; version: string }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/templates/${pathParams.name}/${pathParams.version}`;
   return `/api/templates/${pathParams.name}/${pathParams.version}`;
 });
 });
 
 
@@ -552,7 +569,7 @@ const getApplicationTemplates = baseApi<
   {}
   {}
 >("GET", "/api/templates");
 >("GET", "/api/templates");
 
 
-const getUser = baseApi<{}, { id: number }>("GET", (pathParams) => {
+const getUser = baseApi<{}, { id: number }>("GET", pathParams => {
   return `/api/users/${pathParams.id}`;
   return `/api/users/${pathParams.id}`;
 });
 });
 
 
@@ -561,7 +578,7 @@ const linkGithubProject = baseApi<
   {
   {
     project_id: number;
     project_id: number;
   }
   }
->("GET", (pathParams) => {
+>("GET", pathParams => {
   return `/api/oauth/projects/${pathParams.project_id}/github`;
   return `/api/oauth/projects/${pathParams.project_id}/github`;
 });
 });
 
 
@@ -578,7 +595,7 @@ const provisionECR = baseApi<
     aws_integration_id: string;
     aws_integration_id: string;
   },
   },
   { id: number }
   { id: number }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.id}/provision/ecr`;
   return `/api/projects/${pathParams.id}/provision/ecr`;
 });
 });
 
 
@@ -588,7 +605,7 @@ const provisionEKS = baseApi<
     aws_integration_id: string;
     aws_integration_id: string;
   },
   },
   { id: number }
   { id: number }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   return `/api/projects/${pathParams.id}/provision/eks`;
   return `/api/projects/${pathParams.id}/provision/eks`;
 });
 });
 
 
@@ -608,7 +625,7 @@ const rollbackChart = baseApi<
     name: string;
     name: string;
     cluster_id: number;
     cluster_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   let { id, name, cluster_id } = pathParams;
   let { id, name, cluster_id } = pathParams;
   return `/api/projects/${id}/releases/${name}/rollback?cluster_id=${cluster_id}`;
   return `/api/projects/${id}/releases/${name}/rollback?cluster_id=${cluster_id}`;
 });
 });
@@ -622,7 +639,7 @@ const uninstallTemplate = baseApi<
     namespace: string;
     namespace: string;
     storage: StorageType;
     storage: StorageType;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   let { id, name, cluster_id, storage, namespace } = pathParams;
   let { id, name, cluster_id, storage, namespace } = pathParams;
   return `/api/projects/${id}/delete/${name}?cluster_id=${cluster_id}&namespace=${namespace}&storage=${storage}`;
   return `/api/projects/${id}/delete/${name}?cluster_id=${cluster_id}&namespace=${namespace}&storage=${storage}`;
 });
 });
@@ -633,7 +650,7 @@ const updateUser = baseApi<
     allowedContexts?: string[];
     allowedContexts?: string[];
   },
   },
   { id: number }
   { id: number }
->("PUT", (pathParams) => {
+>("PUT", pathParams => {
   return `/api/users/${pathParams.id}`;
   return `/api/users/${pathParams.id}`;
 });
 });
 
 
@@ -648,7 +665,7 @@ const upgradeChartValues = baseApi<
     name: string;
     name: string;
     cluster_id: number;
     cluster_id: number;
   }
   }
->("POST", (pathParams) => {
+>("POST", pathParams => {
   let { id, name, cluster_id } = pathParams;
   let { id, name, cluster_id } = pathParams;
   return `/api/projects/${id}/releases/${name}/upgrade?cluster_id=${cluster_id}`;
   return `/api/projects/${id}/releases/${name}/upgrade?cluster_id=${cluster_id}`;
 });
 });
@@ -672,8 +689,10 @@ export default {
   createPasswordResetFinalize,
   createPasswordResetFinalize,
   createProject,
   createProject,
   deleteCluster,
   deleteCluster,
+  deleteGitRepoIntegration,
   deleteInvite,
   deleteInvite,
   deleteProject,
   deleteProject,
+  deleteRegistryIntegration,
   createSubdomain,
   createSubdomain,
   deployTemplate,
   deployTemplate,
   destroyEKS,
   destroyEKS,
@@ -721,5 +740,5 @@ export default {
   rollbackChart,
   rollbackChart,
   uninstallTemplate,
   uninstallTemplate,
   updateUser,
   updateUser,
-  upgradeChartValues,
+  upgradeChartValues
 };
 };

+ 6 - 6
dashboard/src/shared/baseApi.tsx

@@ -21,23 +21,23 @@ export const baseApi = <T extends {}, S = {}>(
     if (requestType === "POST") {
     if (requestType === "POST") {
       return axios.post(endpointString, params, {
       return axios.post(endpointString, params, {
         headers: {
         headers: {
-          Authorization: `Bearer ${token}`,
-        },
+          Authorization: `Bearer ${token}`
+        }
       });
       });
     } else if (requestType === "PUT") {
     } else if (requestType === "PUT") {
       return axios.put(endpointString, params, {
       return axios.put(endpointString, params, {
         headers: {
         headers: {
-          Authorization: `Bearer ${token}`,
-        },
+          Authorization: `Bearer ${token}`
+        }
       });
       });
     } else if (requestType === "DELETE") {
     } else if (requestType === "DELETE") {
       return axios.delete(endpointString, params);
       return axios.delete(endpointString, params);
     } else {
     } else {
       return axios.get(endpointString, {
       return axios.get(endpointString, {
         params,
         params,
-        paramsSerializer: function (params) {
+        paramsSerializer: function(params) {
           return qs.stringify(params, { arrayFormat: "repeat" });
           return qs.stringify(params, { arrayFormat: "repeat" });
-        },
+        }
       });
       });
     }
     }
   };
   };

+ 17 - 17
dashboard/src/shared/common.tsx

@@ -10,7 +10,7 @@ export const infraNames: any = {
   gcr: "Google Container Registry (GCR)",
   gcr: "Google Container Registry (GCR)",
   gke: "Google Kubernetes Engine (GKE)",
   gke: "Google Kubernetes Engine (GKE)",
   docr: "Digital Ocean Container Registry",
   docr: "Digital Ocean Container Registry",
-  doks: "Digital Ocean Kubernetes Service",
+  doks: "Digital Ocean Kubernetes Service"
 };
 };
 
 
 export const integrationList: any = {
 export const integrationList: any = {
@@ -18,68 +18,68 @@ export const integrationList: any = {
     icon:
     icon:
       "https://uxwing.com/wp-content/themes/uxwing/download/10-brands-and-social-media/kubernetes.png",
       "https://uxwing.com/wp-content/themes/uxwing/download/10-brands-and-social-media/kubernetes.png",
     label: "Kubernetes",
     label: "Kubernetes",
-    buttonText: "Add a Cluster",
+    buttonText: "Add a Cluster"
   },
   },
   repo: {
   repo: {
     icon:
     icon:
       "https://3.bp.blogspot.com/-xhNpNJJyQhk/XIe4GY78RQI/AAAAAAAAItc/ouueFUj2Hqo5dntmnKqEaBJR4KQ4Q2K3ACK4BGAYYCw/s1600/logo%2Bgit%2Bicon.png",
       "https://3.bp.blogspot.com/-xhNpNJJyQhk/XIe4GY78RQI/AAAAAAAAItc/ouueFUj2Hqo5dntmnKqEaBJR4KQ4Q2K3ACK4BGAYYCw/s1600/logo%2Bgit%2Bicon.png",
     label: "Git Repository",
     label: "Git Repository",
-    buttonText: "Link a Github Account",
+    buttonText: "Link a Github Account"
   },
   },
   registry: {
   registry: {
     icon:
     icon:
       "https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png",
       "https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png",
     label: "Docker Registry",
     label: "Docker Registry",
-    buttonText: "Add a Registry",
+    buttonText: "Add a Registry"
   },
   },
   gke: {
   gke: {
     icon: "https://sysdig.com/wp-content/uploads/2016/08/GKE_color.png",
     icon: "https://sysdig.com/wp-content/uploads/2016/08/GKE_color.png",
-    label: "Google Kubernetes Engine (GKE)",
+    label: "Google Kubernetes Engine (GKE)"
   },
   },
   eks: {
   eks: {
     icon: "https://img.stackshare.io/service/7991/amazon-eks.png",
     icon: "https://img.stackshare.io/service/7991/amazon-eks.png",
-    label: "Amazon Elastic Kubernetes Service (EKS)",
+    label: "Amazon Elastic Kubernetes Service (EKS)"
   },
   },
   kube: {
   kube: {
     icon:
     icon:
       "https://uxwing.com/wp-content/themes/uxwing/download/10-brands-and-social-media/kubernetes.png",
       "https://uxwing.com/wp-content/themes/uxwing/download/10-brands-and-social-media/kubernetes.png",
-    label: "Upload Kubeconfig",
+    label: "Upload Kubeconfig"
   },
   },
   docker: {
   docker: {
     icon:
     icon:
       "https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png",
       "https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png",
-    label: "Docker Hub",
+    label: "Docker Hub"
   },
   },
   gcr: {
   gcr: {
     icon:
     icon:
       "https://carlossanchez.files.wordpress.com/2019/06/21046548.png?w=640",
       "https://carlossanchez.files.wordpress.com/2019/06/21046548.png?w=640",
-    label: "Google Container Registry (GCR)",
+    label: "Google Container Registry (GCR)"
   },
   },
   ecr: {
   ecr: {
     icon:
     icon:
       "https://avatars2.githubusercontent.com/u/52505464?s=400&u=da920f994c67665c7ad6c606a5286557d4f8555f&v=4",
       "https://avatars2.githubusercontent.com/u/52505464?s=400&u=da920f994c67665c7ad6c606a5286557d4f8555f&v=4",
-    label: "Elastic Container Registry (ECR)",
+    label: "Elastic Container Registry (ECR)"
   },
   },
   aws: {
   aws: {
     icon: aws,
     icon: aws,
-    label: "AWS",
+    label: "AWS"
   },
   },
   gcp: {
   gcp: {
     icon: gcp,
     icon: gcp,
-    label: "GCP",
+    label: "GCP"
   },
   },
   do: {
   do: {
     icon: digitalOcean,
     icon: digitalOcean,
-    label: "DigitalOcean",
+    label: "DigitalOcean"
   },
   },
   github: {
   github: {
     icon: github,
     icon: github,
-    label: "GitHub",
+    label: "GitHub"
   },
   },
   gitlab: {
   gitlab: {
     icon: "https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png",
     icon: "https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png",
-    label: "Gitlab",
-  },
+    label: "Gitlab"
+  }
 };
 };
 
 
 export const isAlphanumeric = (x: string | null) => {
 export const isAlphanumeric = (x: string | null) => {
@@ -92,6 +92,6 @@ export const isAlphanumeric = (x: string | null) => {
 
 
 export const getIgnoreCase = (object: any, key: string) => {
 export const getIgnoreCase = (object: any, key: string) => {
   return object[
   return object[
-    Object.keys(object).find((k) => k.toLowerCase() === key.toLowerCase())
+    Object.keys(object).find(k => k.toLowerCase() === key.toLowerCase())
   ];
   ];
 };
 };

+ 5 - 5
dashboard/src/shared/feedback.tsx

@@ -10,18 +10,18 @@ export const handleSubmitFeedback = (
       {
       {
         key: process.env.DISCORD_KEY,
         key: process.env.DISCORD_KEY,
         cid: process.env.DISCORD_CID,
         cid: process.env.DISCORD_CID,
-        message: msg,
+        message: msg
       },
       },
       {
       {
         headers: {
         headers: {
-          Authorization: `Bearer <>`,
-        },
+          Authorization: `Bearer <>`
+        }
       }
       }
     )
     )
-    .then((res) => {
+    .then(res => {
       callback && callback(null, res);
       callback && callback(null, res);
     })
     })
-    .catch((err) => {
+    .catch(err => {
       callback && callback(err, null);
       callback && callback(err, null);
     });
     });
 };
 };

+ 2 - 2
dashboard/src/shared/rosettaStone.tsx

@@ -11,11 +11,11 @@ export const kindToIcon: { [kind: string]: string } = {
   Role: "portrait",
   Role: "portrait",
   RoleBinding: "swap_horizontal_circle",
   RoleBinding: "swap_horizontal_circle",
   ConfigMap: "map",
   ConfigMap: "map",
-  PodSecurityPolicy: "security",
+  PodSecurityPolicy: "security"
 };
 };
 
 
 export const edgeColors: { [kind: string]: string } = {
 export const edgeColors: { [kind: string]: string } = {
   LabelRel: "#32a85f",
   LabelRel: "#32a85f",
   ControlRel: "#fcb603",
   ControlRel: "#fcb603",
-  SpecRel: "#949EFF",
+  SpecRel: "#949EFF"
 };
 };

+ 2 - 2
dashboard/src/shared/routing.tsx

@@ -14,7 +14,7 @@ export const PorterUrls = [
   "integrations",
   "integrations",
   "new-project",
   "new-project",
   "cluster-dashboard",
   "cluster-dashboard",
-  "project-settings",
+  "project-settings"
 ];
 ];
 
 
 export const setSearchParam = (
 export const setSearchParam = (
@@ -26,6 +26,6 @@ export const setSearchParam = (
   urlParams.set(key, value);
   urlParams.set(key, value);
   return {
   return {
     pathname: location.pathname,
     pathname: location.pathname,
-    search: urlParams.toString(),
+    search: urlParams.toString()
   };
   };
 };
 };

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

@@ -66,7 +66,7 @@ export interface EdgeType {
 export enum StorageType {
 export enum StorageType {
   Secret = "secret",
   Secret = "secret",
   ConfigMap = "configmap",
   ConfigMap = "configmap",
-  Memory = "memory",
+  Memory = "memory"
 }
 }
 
 
 // PorterTemplate represents a bundled Porter template
 // PorterTemplate represents a bundled Porter template

+ 1 - 1
internal/models/gitrepo.go

@@ -14,7 +14,7 @@ type GitRepo struct {
 	ProjectID uint `json:"project_id"`
 	ProjectID uint `json:"project_id"`
 
 
 	// The username/organization that this repo integration is linked to
 	// The username/organization that this repo integration is linked to
-	RepoEntity string `json:"repo_entity" gorm:"unique"`
+	RepoEntity string `json:"repo_entity"`
 
 
 	// The various auth mechanisms available to the integration
 	// The various auth mechanisms available to the integration
 	OAuthIntegrationID uint
 	OAuthIntegrationID uint

+ 20 - 8
server/api/git_repo_handler.go

@@ -70,21 +70,33 @@ func (app *App) HandleListRepos(w http.ResponseWriter, r *http.Request) {
 
 
 	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
 	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
 
 
-	// list all repositories for specified user
-	repos, _, err := client.Repositories.List(context.Background(), "", &github.RepositoryListOptions{
+	allRepos := make([]*github.Repository, 0)
+
+	opt := &github.RepositoryListOptions{
 		ListOptions: github.ListOptions{
 		ListOptions: github.ListOptions{
 			PerPage: 100,
 			PerPage: 100,
 		},
 		},
 		Sort: "updated",
 		Sort: "updated",
-	})
+	}
 
 
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
+	for {
+		repos, resp, err := client.Repositories.List(context.Background(), "", opt)
+
+		if err != nil {
+			app.handleErrorInternal(err, w)
+			return
+		}
+
+		allRepos = append(allRepos, repos...)
+
+		if resp.NextPage == 0 {
+			break
+		}
+
+		opt.Page = resp.NextPage
 	}
 	}
 
 
-	// TODO -- check if repo has already been appended -- there may be duplicates
-	for _, repo := range repos {
+	for _, repo := range allRepos {
 		res = append(res, Repo{
 		res = append(res, Repo{
 			FullName: repo.GetFullName(),
 			FullName: repo.GetFullName(),
 			Kind:     "github",
 			Kind:     "github",