Explorar o código

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

Promisify API refactor
abelanger5 %!s(int64=5) %!d(string=hai) anos
pai
achega
a9692538d5
Modificáronse 39 ficheiros con 1699 adicións e 1856 borrados
  1. 131 121
      dashboard/src/components/image-selector/ImageList.tsx
  2. 65 72
      dashboard/src/components/image-selector/ImageSelector.tsx
  3. 19 19
      dashboard/src/components/image-selector/TagList.tsx
  4. 25 18
      dashboard/src/components/repo-selector/BranchList.tsx
  5. 62 75
      dashboard/src/components/repo-selector/ButtonTray.tsx
  6. 45 35
      dashboard/src/components/repo-selector/ContentsList.tsx
  7. 47 52
      dashboard/src/components/repo-selector/RepoList.tsx
  8. 15 14
      dashboard/src/main/Login.tsx
  9. 15 12
      dashboard/src/main/Main.tsx
  10. 13 11
      dashboard/src/main/Register.tsx
  11. 91 136
      dashboard/src/main/home/Home.tsx
  12. 16 13
      dashboard/src/main/home/cluster-dashboard/NamespaceSelector.tsx
  13. 68 68
      dashboard/src/main/home/cluster-dashboard/chart/ChartList.tsx
  14. 123 139
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx
  15. 46 49
      dashboard/src/main/home/cluster-dashboard/expanded-chart/RevisionSection.tsx
  16. 79 66
      dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx
  17. 21 21
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ValuesYaml.tsx
  18. 17 16
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx
  19. 26 25
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx
  20. 8 12
      dashboard/src/main/home/dashboard/ClusterList.tsx
  21. 9 13
      dashboard/src/main/home/dashboard/Dashboard.tsx
  22. 99 91
      dashboard/src/main/home/integrations/Integrations.tsx
  23. 24 20
      dashboard/src/main/home/integrations/integration-form/ECRForm.tsx
  24. 76 56
      dashboard/src/main/home/integrations/integration-form/GCRForm.tsx
  25. 17 24
      dashboard/src/main/home/modals/IntegrationsModal.tsx
  26. 46 61
      dashboard/src/main/home/modals/UpdateClusterModal.tsx
  27. 4 3
      dashboard/src/main/home/navbar/Navbar.tsx
  28. 48 66
      dashboard/src/main/home/project-settings/InviteList.tsx
  29. 67 93
      dashboard/src/main/home/provisioner/AWSFormSection.tsx
  30. 25 34
      dashboard/src/main/home/provisioner/DOFormSection.tsx
  31. 23 32
      dashboard/src/main/home/provisioner/ExistingClusterSection.tsx
  32. 74 82
      dashboard/src/main/home/provisioner/GCPFormSection.tsx
  33. 54 53
      dashboard/src/main/home/provisioner/Provisioner.tsx
  34. 34 41
      dashboard/src/main/home/sidebar/ClusterSection.tsx
  35. 5 6
      dashboard/src/main/home/templates/Templates.tsx
  36. 21 23
      dashboard/src/main/home/templates/expanded-template/ExpandedTemplate.tsx
  37. 120 132
      dashboard/src/main/home/templates/expanded-template/LaunchTemplate.tsx
  38. 18 51
      dashboard/src/shared/baseApi.tsx
  39. 3 1
      server/api/git_repo_handler.go

+ 131 - 121
dashboard/src/components/image-selector/ImageList.tsx

@@ -1,28 +1,28 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
+import React, { Component } from "react";
+import styled from "styled-components";
 
-import api from '../../shared/api';
-import { integrationList } from '../../shared/common';
-import { Context } from '../../shared/Context';
-import { ImageType } from '../../shared/types';
+import api from "shared/api";
+import { integrationList } from "shared/common";
+import { Context } from "shared/Context";
+import { ImageType } from "shared/types";
 
-import Loading from '../Loading';
-import TagList from './TagList';
+import Loading from "../Loading";
+import TagList from "./TagList";
 
 type PropsType = {
-  selectedImageUrl: string | null,
-  selectedTag: string | null,
-  clickedImage: ImageType | null,
-  registry?: any,
-  setSelectedImageUrl: (x: string) => void,
-  setSelectedTag: (x: string) => void,
-  setClickedImage: (x: ImageType) => void,
+  selectedImageUrl: string | null;
+  selectedTag: string | null;
+  clickedImage: ImageType | null;
+  registry?: any;
+  setSelectedImageUrl: (x: string) => void;
+  setSelectedTag: (x: string) => void;
+  setClickedImage: (x: ImageType) => void;
 };
 
 type StateType = {
-  loading: boolean,
-  error: boolean,
-  images: ImageType[],
+  loading: boolean;
+  error: boolean;
+  images: ImageType[];
 };
 
 export default class ImageSelector extends Component<PropsType, StateType> {
@@ -30,116 +30,127 @@ export default class ImageSelector extends Component<PropsType, StateType> {
     loading: true,
     error: false,
     images: [] as ImageType[],
-  }
+  };
 
   componentDidMount() {
     const { currentProject, setCurrentError } = this.context;
-    let images = [] as ImageType[]
-    let errors = [] as number[]
+    let images = [] as ImageType[];
+    let errors = [] as number[];
     if (!this.props.registry) {
-      api.getProjectRegistries('<token>', {}, { id: currentProject.id }, async (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          this.setState({ loading: false, error: true });
-        } else {
+      api
+        .getProjectRegistries("<token>", {}, { id: currentProject.id })
+        .then((res) => {
           let registries = res.data;
           if (registries.length === 0) {
             this.setState({ loading: false });
           }
           // Loop over connected image registries
+          // TODO: promise.map the whole thing
           registries.forEach(async (registry: any, i: number) => {
-            await new Promise((nextController: (res?: any) => void) => {           
-              api.getImageRepos('<token>', {}, 
-                { 
-                  project_id: currentProject.id,
-                  registry_id: registry.id,
-                }, (err: any, res: any) => {
-                if (err) {
-                  errors.push(1);
-                } else {
-                  res.data.sort((a: any, b: any) => (a.name > b.name) ? 1 : -1);
-                  // Loop over found image repositories
-                  let newImg = res.data.map((img: any) => {
-                    if (this.props.selectedImageUrl === img.uri) {
-                      this.props.setClickedImage(
-                        {
+            await new Promise(
+              (resolveToNextController: (res?: any) => void) => {
+                api
+                  .getImageRepos(
+                    "<token>",
+                    {},
+                    {
+                      project_id: currentProject.id,
+                      registry_id: registry.id,
+                    }
+                  )
+                  .then((res) => {
+                    res.data.sort((a: any, b: any) =>
+                      a.name > b.name ? 1 : -1
+                    );
+                    // Loop over found image repositories
+                    let newImg = res.data.map((img: any) => {
+                      if (this.props.selectedImageUrl === img.uri) {
+                        this.props.setClickedImage({
                           kind: registry.service,
                           source: img.uri,
                           name: img.name,
                           registryId: registry.id,
-                        }
-                      );
-                    }
-                    return {
-                      kind: registry.service, 
-                      source: img.uri,
-                      name: img.name,
-                      registryId: registry.id,
-                    }
+                        });
+                      }
+                      return {
+                        kind: registry.service,
+                        source: img.uri,
+                        name: img.name,
+                        registryId: registry.id,
+                      };
+                    });
+                    images.push(...newImg);
+                    errors.push(0);
                   })
-                  images.push(...newImg)
-                  errors.push(0);
-                }
-                
-                if (i == registries.length - 1) {
-                  let error = errors.reduce((a, b) => {
-                    return a + b;
-                  }) == registries.length ? true : false; 
-  
-                  this.setState({
-                    images,
-                    loading: false,
-                    error,
+                  .catch((err) => errors.push(1))
+                  .finally(() => {
+                    if (i == registries.length - 1) {
+                      let error =
+                        errors.reduce((a, b) => {
+                          return a + b;
+                        }) == registries.length
+                          ? true
+                          : false;
+
+                      this.setState({
+                        images,
+                        loading: false,
+                        error,
+                      });
+                    }
+                    resolveToNextController();
                   });
-                }
-  
-                nextController()
-              });
-            })
+              }
+            );
           });
-        }
-      });
+        })
+        .catch((err) => {
+          console.log(err);
+          this.setState({ loading: false, error: true });
+        });
     } else {
-      api.getImageRepos('<token>', {}, 
-      { 
-        project_id: currentProject.id,
-        registry_id: this.props.registry.id,
-      }, (err: any, res: any) => {
-        if (err) {
-          this.setState({
-            loading: false,
-            error: true,
-          });
-        } else {
-          res.data.sort((a: any, b: any) => (a.name > b.name) ? 1 : -1);
+      api
+        .getImageRepos(
+          "<token>",
+          {},
+          {
+            project_id: currentProject.id,
+            registry_id: this.props.registry.id,
+          }
+        )
+        .then((res) => {
+          res.data.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
           // Loop over found image repositories
           let newImg = res.data.map((img: any) => {
             if (this.props.selectedImageUrl === img.uri) {
-              this.props.setClickedImage(
-                {
-                  kind: this.props.registry.service,
-                  source: img.uri,
-                  name: img.name,
-                  registryId: this.props.registry.id,
-                }
-              );
+              this.props.setClickedImage({
+                kind: this.props.registry.service,
+                source: img.uri,
+                name: img.name,
+                registryId: this.props.registry.id,
+              });
             }
             return {
-              kind: this.props.registry.service, 
+              kind: this.props.registry.service,
               source: img.uri,
               name: img.name,
               registryId: this.props.registry.id,
-            }
-          })
-          images.push(...newImg)
+            };
+          });
+          images.push(...newImg);
 
           this.setState({
             images,
             loading: false,
             error: false,
           });
-        }
-      });
+        })
+        .catch((err) =>
+          this.setState({
+            loading: false,
+            error: true,
+          })
+        );
     }
   }
 
@@ -151,46 +162,48 @@ export default class ImageSelector extends Component<PropsType, StateType> {
   renderImageList = () => {
     let { images, loading, error } = this.state;
     if (loading) {
-      return <LoadingWrapper><Loading /></LoadingWrapper>
-    } else if (error || !images) {
-      return <LoadingWrapper>Error loading repos</LoadingWrapper>
-    } else if (images.length === 0) {
       return (
         <LoadingWrapper>
-          No registries found. 
+          <Loading />
         </LoadingWrapper>
       );
+    } else if (error || !images) {
+      return <LoadingWrapper>Error loading repos</LoadingWrapper>;
+    } else if (images.length === 0) {
+      return <LoadingWrapper>No registries found.</LoadingWrapper>;
     }
 
     return images.map((image: ImageType, i: number) => {
-      let icon = integrationList[image.kind] && integrationList[image.kind].icon;
+      let icon =
+        integrationList[image.kind] && integrationList[image.kind].icon;
       if (!icon) {
-        icon = integrationList['docker'].icon;
+        icon = integrationList["docker"].icon;
       }
       return (
         <ImageItem
           key={i}
           isSelected={image.source === this.props.selectedImageUrl}
           lastItem={i === images.length - 1}
-          onClick={() => { 
+          onClick={() => {
             this.props.setSelectedImageUrl(image.source);
             this.props.setClickedImage(image);
           }}
         >
-          <img src={icon && icon} />{image.source}
+          <img src={icon && icon} />
+          {image.source}
         </ImageItem>
       );
     });
-  }
+  };
 
   renderBackButton = () => {
     let { setSelectedImageUrl } = this.props;
     if (this.props.clickedImage) {
       return (
         <BackButton
-          width='175px'
+          width="175px"
           onClick={() => {
-            setSelectedImageUrl('');
+            setSelectedImageUrl("");
             this.props.setClickedImage(null);
           }}
         >
@@ -199,16 +212,14 @@ export default class ImageSelector extends Component<PropsType, StateType> {
         </BackButton>
       );
     }
-  }
+  };
 
   renderExpanded = () => {
     let { selectedTag, selectedImageUrl, setSelectedTag } = this.props;
     if (!this.props.clickedImage) {
       return (
         <div>
-          <ExpandedWrapper>
-            {this.renderImageList()}
-          </ExpandedWrapper>
+          <ExpandedWrapper>{this.renderImageList()}</ExpandedWrapper>
           {this.renderBackButton()}
         </div>
       );
@@ -227,14 +238,10 @@ export default class ImageSelector extends Component<PropsType, StateType> {
         </div>
       );
     }
-  }
+  };
 
   render() {
-    return (
-      <>
-        {this.renderExpanded()}
-      </>
-    );
+    return <>{this.renderExpanded()}</>;
   }
 }
 
@@ -269,13 +276,16 @@ const ImageItem = styled.div`
   display: flex;
   width: 100%;
   font-size: 13px;
-  border-bottom: 1px solid ${(props: { lastItem: boolean, isSelected: boolean }) => props.lastItem ? '#00000000' : '#606166'};
+  border-bottom: 1px solid
+    ${(props: { lastItem: boolean; isSelected: boolean }) =>
+      props.lastItem ? "#00000000" : "#606166"};
   color: #ffffff;
   user-select: none;
   align-items: center;
   padding: 10px 0px;
   cursor: pointer;
-  background: ${(props: { isSelected: boolean, lastItem: boolean }) => props.isSelected ? '#ffffff11' : ''};
+  background: ${(props: { isSelected: boolean; lastItem: boolean }) =>
+    props.isSelected ? "#ffffff11" : ""};
   :hover {
     background: #ffffff22;
 
@@ -310,4 +320,4 @@ const ExpandedWrapper = styled.div`
   max-height: 275px;
   background: #ffffff11;
   overflow-y: auto;
-`;
+`;

+ 65 - 72
dashboard/src/components/image-selector/ImageSelector.tsx

@@ -8,9 +8,9 @@ import { integrationList } from "shared/common";
 import { Context } from "shared/Context";
 import { ImageType } from "shared/types";
 
-import Loading from '../Loading';
-import TagList from './TagList';
-import ImageList from './ImageList';
+import Loading from "../Loading";
+import TagList from "./TagList";
+import ImageList from "./ImageList";
 
 type PropsType = {
   forceExpanded?: boolean;
@@ -41,83 +41,76 @@ export default class ImageSelector extends Component<PropsType, StateType> {
     const { currentProject, setCurrentError } = this.context;
     let images = [] as ImageType[];
     let errors = [] as number[];
-    api.getProjectRegistries(
-      "<token>",
-      {},
-      { id: currentProject.id },
-      async (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          this.setState({ error: true });
-        } else {
-          let registries = res.data;
-          if (registries.length === 0) {
-            this.setState({ loading: false });
-          }
-
-          // Loop over connected image registries
-          registries.forEach(async (registry: any, i: number) => {
-            await new Promise((nextController: (res?: any) => void) => {
-              api.getImageRepos(
+    api
+      .getProjectRegistries("<token>", {}, { id: currentProject.id })
+      .then(async (res) => {
+        let registries = res.data;
+        if (registries.length === 0) {
+          this.setState({ loading: false });
+        }
+
+        // Loop over connected image registries
+        registries.forEach(async (registry: any, i: number) => {
+          await new Promise((nextController: (res?: any) => void) => {
+            api
+              .getImageRepos(
                 "<token>",
                 {},
                 {
                   project_id: currentProject.id,
                   registry_id: registry.id,
-                },
-                (err: any, res: any) => {
-                  if (err) {
-                    errors.push(1);
-                  } else {
-                    res.data.sort((a: any, b: any) =>
-                      a.name > b.name ? 1 : -1
-                    );
-                    // Loop over found image repositories
-                    let newImg = res.data.map((img: any) => {
-                      if (this.props.selectedImageUrl === img.uri) {
-                        this.setState({
-                          clickedImage: {
-                            kind: registry.service,
-                            source: img.uri,
-                            name: img.name,
-                            registryId: registry.id,
-                          },
-                        });
-                      }
-                      return {
+                }
+              )
+              .then((res) => {
+                res.data.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
+                // Loop over found image repositories
+                let newImg = res.data.map((img: any) => {
+                  if (this.props.selectedImageUrl === img.uri) {
+                    this.setState({
+                      clickedImage: {
                         kind: registry.service,
                         source: img.uri,
                         name: img.name,
                         registryId: registry.id,
-                      };
+                      },
                     });
-                    images.push(...newImg);
-                    errors.push(0);
                   }
-
-                  if (i == registries.length - 1) {
-                    let error =
-                      errors.reduce((a, b) => {
-                        return a + b;
-                      }) == registries.length
-                        ? true
-                        : false;
-
-                    this.setState({
-                      images,
-                      loading: false,
-                      error,
-                    });
-                  }
-
-                  nextController();
+                  return {
+                    kind: registry.service,
+                    source: img.uri,
+                    name: img.name,
+                    registryId: registry.id,
+                  };
+                });
+                images.push(...newImg);
+                errors.push(0);
+              })
+              .catch(() => errors.push(1))
+              .finally(() => {
+                if (i == registries.length - 1) {
+                  let error =
+                    errors.reduce((a, b) => {
+                      return a + b;
+                    }) == registries.length
+                      ? true
+                      : false;
+
+                  this.setState({
+                    images,
+                    loading: false,
+                    error,
+                  });
                 }
-              );
-            });
+
+                nextController();
+              });
           });
-        }
-      }
-    );
+        });
+      })
+      .catch((err) => {
+        console.log(err);
+        this.setState({ error: true });
+      });
   }
 
   /*
@@ -259,18 +252,18 @@ export default class ImageSelector extends Component<PropsType, StateType> {
           )}
         </StyledImageSelector>
 
-        {this.state.isExpanded
-          ?
+        {this.state.isExpanded ? (
           <ImageList
             selectedImageUrl={this.props.selectedImageUrl}
             selectedTag={this.props.selectedTag}
             clickedImage={this.state.clickedImage}
             setSelectedImageUrl={this.props.setSelectedImageUrl}
             setSelectedTag={this.props.setSelectedTag}
-            setClickedImage={(x: ImageType) => this.setState({ clickedImage: x })}
+            setClickedImage={(x: ImageType) =>
+              this.setState({ clickedImage: x })
+            }
           />
-          : null
-        }
+        ) : null}
       </div>
     );
   }

+ 19 - 19
dashboard/src/components/image-selector/TagList.tsx

@@ -34,26 +34,26 @@ export default class TagList extends Component<PropsType, StateType> {
     const { currentProject } = this.context;
     let splits = this.props.selectedImageUrl.split("/");
     let repoName = splits[splits.length - 1];
-    api.getImageTags(
-      "<token>",
-      {},
-      {
-        project_id: currentProject.id,
-        registry_id: this.props.registryId,
-        repo_name: repoName,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          this.setState({ loading: false, error: true });
-        } else {
-          let tags = res.data.map((tag: any, i: number) => {
-            return tag.tag;
-          });
-          this.setState({ tags, loading: false });
+    api
+      .getImageTags(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          registry_id: this.props.registryId,
+          repo_name: repoName,
         }
-      }
-    );
+      )
+      .then((res) => {
+        let tags = res.data.map((tag: any, i: number) => {
+          return tag.tag;
+        });
+        this.setState({ tags, loading: false });
+      })
+      .catch((err) => {
+        console.log(err);
+        this.setState({ loading: false, error: true });
+      });
   }
 
   setTag = (tag: string) => {

+ 25 - 18
dashboard/src/components/repo-selector/BranchList.tsx

@@ -2,15 +2,15 @@ import React, { Component } from "react";
 import styled from "styled-components";
 import branch_icon from "assets/branch.png";
 
-import api from '../../shared/api';
-import { Context } from '../../shared/Context';
-import { ActionConfigType } from '../..//shared/types';
+import api from "../../shared/api";
+import { Context } from "../../shared/Context";
+import { ActionConfigType } from "../..//shared/types";
 
 import Loading from "../Loading";
 
 type PropsType = {
-  actionConfig: ActionConfigType,
-  setBranch: (x: string) => void,
+  actionConfig: ActionConfigType;
+  setBranch: (x: string) => void;
 };
 
 type StateType = {
@@ -31,20 +31,25 @@ export default class BranchList extends Component<PropsType, StateType> {
     let { currentProject } = this.context;
 
     // Get branches
-    api.getBranches('<token>', {}, {
-      project_id: currentProject.id,
-      git_repo_id: actionConfig.git_repo_id,
-      kind: 'github',
-      owner: actionConfig.git_repo.split('/')[0],
-      name: actionConfig.git_repo.split('/')[1],
-    }, (err: any, res: any) => {
-      if (err) {
+    api
+      .getBranches(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          git_repo_id: actionConfig.git_repo_id,
+          kind: "github",
+          owner: actionConfig.git_repo.split("/")[0],
+          name: actionConfig.git_repo.split("/")[1],
+        }
+      )
+      .then((res) =>
+        this.setState({ branches: res.data, loading: false, error: false })
+      )
+      .catch((err) => {
         console.log(err);
         this.setState({ loading: false, error: true });
-      } else {
-        this.setState({ branches: res.data, loading: false, error: false });
-      }
-    });
+      });
   }
 
   renderBranchList = () => {
@@ -84,7 +89,9 @@ const BranchName = styled.div`
   display: flex;
   width: 100%;
   font-size: 13px;
-  border-bottom: 1px solid ${(props: { lastItem: boolean }) => props.lastItem ? '#00000000' : '#606166'};
+  border-bottom: 1px solid
+    ${(props: { lastItem: boolean }) =>
+      props.lastItem ? "#00000000" : "#606166"};
   color: #ffffff;
   user-select: none;
   align-items: center;

+ 62 - 75
dashboard/src/components/repo-selector/ButtonTray.tsx

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

+ 45 - 35
dashboard/src/components/repo-selector/ContentsList.tsx

@@ -4,17 +4,17 @@ import file from "assets/file.svg";
 import folder from "assets/folder.svg";
 import info from "assets/info.svg";
 
-import api from '../../shared/api';
-import { Context } from '../../shared/Context';
-import { FileType, ActionConfigType } from '../../shared/types';
+import api from "../../shared/api";
+import { Context } from "../../shared/Context";
+import { FileType, ActionConfigType } from "../../shared/types";
 
 import Loading from "../Loading";
 
 type PropsType = {
-  actionConfig: ActionConfigType | null,
-  branch: string,
-  setActionConfig: (x: ActionConfigType) => void,
-  setPath: () => void,
+  actionConfig: ActionConfigType | null;
+  branch: string;
+  setActionConfig: (x: ActionConfigType) => void;
+  setPath: () => void;
 };
 
 type StateType = {
@@ -36,38 +36,48 @@ export default class ContentsList extends Component<PropsType, StateType> {
     updatedConfig.dockerfile_path = x;
     setActionConfig(updatedConfig);
     this.updateContents();
-  }
+  };
 
   updateContents = () => {
     let { actionConfig, branch } = this.props;
     let { currentProject } = this.context;
 
     // Get branch contents
-    api.getBranchContents('<token>', { dir: actionConfig.dockerfile_path }, {
-      project_id: currentProject.id,
-      git_repo_id: actionConfig.git_repo_id,
-      kind: 'github',
-      owner: actionConfig.git_repo.split('/')[0],
-      name: actionConfig.git_repo.split('/')[1],
-      branch: branch,
-    }, (err: any, res: any) => {
-      if (err) {
-        console.log(err);
-        this.setState({ loading: false, error: true });
-      } else {
+    api
+      .getBranchContents(
+        "<token>",
+        { dir: actionConfig.dockerfile_path },
+        {
+          project_id: currentProject.id,
+          git_repo_id: actionConfig.git_repo_id,
+          kind: "github",
+          owner: actionConfig.git_repo.split("/")[0],
+          name: actionConfig.git_repo.split("/")[1],
+          branch: branch,
+        }
+      )
+      .then((res) => {
         let files = [] as FileType[];
         let folders = [] as FileType[];
         res.data.map((x: FileType, i: number) => {
-          x.Type === 'dir' ? folders.push(x) : files.push(x);
+          x.Type === "dir" ? folders.push(x) : files.push(x);
         });
 
-        folders.sort((a: FileType, b: FileType) => { return a.Path < b.Path ? 1 : 0 });
-        files.sort((a: FileType, b: FileType) => { return a.Path < b.Path ? 1 : 0 });
+        folders.sort((a: FileType, b: FileType) => {
+          return a.Path < b.Path ? 1 : 0;
+        });
+        files.sort((a: FileType, b: FileType) => {
+          return a.Path < b.Path ? 1 : 0;
+        });
         let contents = folders.concat(files);
-        
+
         this.setState({ contents, loading: false, error: false });
-      }
-    });
+      })
+      .catch((err) => {
+        console.log(err);
+
+        this.setState({ loading: false, error: true });
+      });
   };
 
   componentDidMount() {
@@ -127,21 +137,21 @@ export default class ContentsList extends Component<PropsType, StateType> {
 
   renderJumpToParent = () => {
     let { actionConfig } = this.props;
-    if (actionConfig.dockerfile_path !== '') {
-      let splits = actionConfig.dockerfile_path.split('/');
-      let subdir = '';
+    if (actionConfig.dockerfile_path !== "") {
+      let splits = actionConfig.dockerfile_path.split("/");
+      let subdir = "";
       if (splits.length !== 1) {
-        subdir = actionConfig.dockerfile_path.replace(splits[splits.length - 1], '');
-        if (subdir.charAt(subdir.length - 1) === '/') {
+        subdir = actionConfig.dockerfile_path.replace(
+          splits[splits.length - 1],
+          ""
+        );
+        if (subdir.charAt(subdir.length - 1) === "/") {
           subdir = subdir.slice(0, subdir.length - 1);
         }
       }
 
       return (
-        <Item
-          lastItem={false}
-          onClick={() => this.setSubdirectory(subdir)}
-        >
+        <Item lastItem={false} onClick={() => this.setSubdirectory(subdir)}>
           <BackLabel>..</BackLabel>
         </Item>
       );

+ 47 - 52
dashboard/src/components/repo-selector/RepoList.tsx

@@ -33,63 +33,58 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
 
     // Get repos
     if (!this.props.userId && this.props.userId !== 0) {
-      api.getGitRepos(
-        "<token>",
-        {},
-        { project_id: currentProject.id },
-        (err: any, res: any) => {
-          if (err) {
-            this.setState({ loading: false, error: true });
-          } else {
-            var allRepos: any = [];
-            for (let i = 0; i < res.data.length; i++) {
-              var grid = res.data[i].id;
-              api.getGitRepoList(
+      api
+        .getGitRepos("<token>", {}, { project_id: currentProject.id })
+        .then((res) => {
+          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 },
-                (err: any, res: any) => {
-                  if (err) {
-                    console.log(err);
-                    this.setState({ loading: false, error: true });
-                  } else {
-                    res.data.forEach((repo: any, id: number) => {
-                      repo.GHRepoID = grid;
-                    });
-                    allRepos = allRepos.concat(res.data);
-                    this.setState({
-                      repos: allRepos,
-                      loading: false,
-                      error: false,
-                    });
-                  }
-                }
-              );
-            }
-            if (res.data.length < 1) {
-              this.setState({ loading: false, error: false });
-            }
+                { 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);
+                this.setState({
+                  repos: allRepos,
+                  loading: false,
+                  error: false,
+                });
+              })
+              .catch((err) => {
+                console.log(err);
+                this.setState({ loading: false, error: true });
+              });
           }
-        }
-      );
+          if (res.data.length < 1) {
+            this.setState({ loading: false, error: false });
+          }
+        })
+        .catch((err) => this.setState({ loading: false, error: true }));
     } else {
       let grid = this.props.userId;
-      api.getGitRepoList(
-        "<token>",
-        {},
-        { project_id: currentProject.id, git_repo_id: grid },
-        (err: any, res: any) => {
-          if (err) {
-            console.log(err);
-            this.setState({ loading: false, error: true });
-          } else {
-            res.data.forEach((repo: any, id: number) => {
-              repo.GHRepoID = grid;
-            });
-            this.setState({ repos: res.data, loading: false, error: false });
-          }
-        }
-      );
+      api
+        .getGitRepoList(
+          "<token>",
+          {},
+          { project_id: currentProject.id, git_repo_id: grid }
+        )
+        .then((res) => {
+          res.data.forEach((repo: any, id: number) => {
+            repo.GHRepoID = grid;
+          });
+          this.setState({ repos: res.data, loading: false, error: false });
+        })
+        .catch((err) => {
+          console.log(err);
+          this.setState({ loading: false, error: true });
+        });
     }
   }
 

+ 15 - 14
dashboard/src/main/Login.tsx

@@ -52,26 +52,27 @@ export default class Login extends Component<PropsType, StateType> {
       this.setState({ emailError: true });
     } else {
       // Attempt user login
-      api.logInUser(
-        "",
-        {
-          email: email,
-          password: password,
-        },
-        {},
-        (err: any, res: any) => {
+      api
+        .logInUser(
+          "",
+          {
+            email: email,
+            password: password,
+          },
+          {}
+        )
+        .then((res) => {
           // TODO: case and set credential error
-          if (err) {
-            this.context.setCurrentError(err.response.data.errors[0]);
-          }
           if (res?.data?.redirect) {
             window.location.href = res.data.redirect;
           } else {
             setUser(res?.data?.id, res?.data?.email);
-            err ? console.log(err.response.data) : authenticate();
+            authenticate();
           }
-        }
-      );
+        })
+        .catch((err) =>
+          this.context.setCurrentError(err.response.data.errors[0])
+        );
     }
   };
 

+ 15 - 12
dashboard/src/main/Main.tsx

@@ -32,18 +32,21 @@ export default class Main extends Component<PropsType, StateType> {
     let urlParams = new URLSearchParams(window.location.search);
     let error = urlParams.get("error");
     error && setCurrentError(error);
-    api.checkAuth("", {}, {}, (err: any, res: any) => {
-      if (err && err.response?.status == 403) {
-        this.setState({ isLoggedIn: false, loading: false });
-      }
-
-      if (res && res.data) {
-        setUser(res?.data?.id, res?.data?.email);
-        this.setState({ isLoggedIn: true, initialized: true, loading: false });
-      } else {
-        this.setState({ isLoggedIn: false, loading: false });
-      }
-    });
+    api
+      .checkAuth("", {}, {})
+      .then((res) => {
+        if (res && res.data) {
+          setUser(res?.data?.id, res?.data?.email);
+          this.setState({
+            isLoggedIn: true,
+            initialized: true,
+            loading: false,
+          });
+        } else {
+          this.setState({ isLoggedIn: false, loading: false });
+        }
+      })
+      .catch((err) => this.setState({ isLoggedIn: false, loading: false }));
   }
 
   initialize = () => {

+ 13 - 11
dashboard/src/main/Register.tsx

@@ -61,22 +61,24 @@ export default class Register extends Component<PropsType, StateType> {
     // Check for valid input
     if (emailRegex.test(email) && confirmPassword === password) {
       // Attempt user registration
-      api.registerUser(
-        "",
-        {
-          email: email,
-          password: password,
-        },
-        {},
-        (err: any, res: any) => {
+      api
+        .registerUser(
+          "",
+          {
+            email: email,
+            password: password,
+          },
+          {}
+        )
+        .then((res: any) => {
           if (res?.data?.redirect) {
             window.location.href = res.data.redirect;
           } else {
             setUser(res?.data?.id, res?.data?.email);
-            err ? setCurrentError(err.response.data.errors[0]) : authenticate();
+            authenticate();
           }
-        }
-      );
+        })
+        .catch((err) => setCurrentError(err.response.data.errors[0]));
     }
   };
 

+ 91 - 136
dashboard/src/main/home/Home.tsx

@@ -61,14 +61,15 @@ class Home extends Component<PropsType, StateType> {
 
     if (!currentProject) return;
 
-    api.getInfra(
-      "<token>",
-      {},
-      {
-        project_id: currentProject.id,
-      },
-      (err: any, res: any) => {
-        if (err) return;
+    api
+      .getInfra(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+        }
+      )
+      .then((res) => {
         let creating = false;
 
         for (var i = 0; i < res.data.length; i++) {
@@ -80,21 +81,16 @@ class Home extends Component<PropsType, StateType> {
           this.props.history.push("integrations");
           this.setState({ ghRedirect: false });
         }
-      }
-    );
+      });
   };
 
   getProjects = (id?: number) => {
     let { user, setProjects } = this.context;
     let { currentProject } = this.props;
-    api.getProjects(
-      "<token>",
-      {},
-      { id: user.userId },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else if (res.data) {
+    api
+      .getProjects("<token>", {}, { id: user.userId })
+      .then((res) => {
+        if (res.data) {
           if (res.data.length === 0) {
             this.props.history.push("new-project");
           } else if (res.data.length > 0 && !currentProject) {
@@ -125,13 +121,13 @@ class Home extends Component<PropsType, StateType> {
             }
           }
         }
-      }
-    );
+      })
+      .catch(console.log);
   };
 
   provisionDOCR = (integrationId: number, tier: string, callback?: any) => {
     console.log("Provisioning DOCR...");
-    api.createDOCR(
+    return api.createDOCR(
       "<token>",
       {
         do_integration_id: integrationId,
@@ -140,53 +136,39 @@ class Home extends Component<PropsType, StateType> {
       },
       {
         project_id: this.props.currentProject.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          return;
-        }
-        callback && callback();
       }
     );
   };
 
   provisionDOKS = (integrationId: number, region: string) => {
     console.log("Provisioning DOKS...");
-    api.createDOKS(
-      "<token>",
-      {
-        do_integration_id: integrationId,
-        doks_name: this.props.currentProject.name,
-        do_region: region,
-      },
-      {
-        project_id: this.props.currentProject.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          return;
+    return api
+      .createDOKS(
+        "<token>",
+        {
+          do_integration_id: integrationId,
+          doks_name: this.props.currentProject.name,
+          do_region: region,
+        },
+        {
+          project_id: this.props.currentProject.id,
         }
-        this.props.history.push("dashboard?tab=provisioner");
-      }
-    );
+      )
+      .then(() => this.props.history.push("dashboard?tab=provisioner"));
   };
 
   checkDO = () => {
     let { currentProject } = this.props;
     if (this.state.handleDO && currentProject?.id) {
-      api.getOAuthIds(
-        "<token>",
-        {},
-        {
-          project_id: currentProject.id,
-        },
-        (err: any, res: any) => {
-          if (err) {
-            console.log(err);
-            return;
+      api
+        .getOAuthIds(
+          "<token>",
+          {},
+          {
+            project_id: currentProject.id,
           }
+        )
+        .then((res) => {
           let tgtIntegration = res.data.find((integration: any) => {
             return integration.client === "do";
           });
@@ -206,8 +188,8 @@ class Home extends Component<PropsType, StateType> {
           } else {
             this.provisionDOKS(tgtIntegration.id, region);
           }
-        }
-      );
+        })
+        .catch(console.log);
       this.setState({ handleDO: false });
     }
   };
@@ -358,14 +340,10 @@ class Home extends Component<PropsType, StateType> {
 
   projectOverlayCall = () => {
     let { user, setProjects } = this.context;
-    api.getProjects(
-      "<token>",
-      {},
-      { id: user.userId },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else if (res.data) {
+    api
+      .getProjects("<token>", {}, { id: user.userId })
+      .then((res) => {
+        if (res.data) {
           setProjects(res.data);
           if (res.data.length > 0) {
             this.context.setCurrentProject(res.data[0]);
@@ -375,37 +353,23 @@ class Home extends Component<PropsType, StateType> {
           }
           this.context.setCurrentModal(null, null);
         }
-      }
-    );
+      })
+      .catch(console.log);
   };
 
   handleDelete = () => {
     let { setCurrentModal, currentProject } = this.context;
     localStorage.removeItem(currentProject.id + "-cluster");
-    api.deleteProject(
-      "<token>",
-      {},
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          this.projectOverlayCall();
-        }
-      }
-    );
+    api
+      .deleteProject("<token>", {}, { id: currentProject.id })
+      .then(this.projectOverlayCall)
+      .catch(console.log);
 
     // Loop through and delete infra of all clusters we've provisioned
-    api.getClusters(
-      "<token>",
-      {},
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          return;
-        }
-
+    api
+      .getClusters("<token>", {}, { id: currentProject.id })
+      .then((res) => {
+        // TODO: promise.map
         for (var i = 0; i < res.data.length; i++) {
           let cluster = res.data[i];
           if (!cluster.infra_id) continue;
@@ -413,65 +377,56 @@ class Home extends Component<PropsType, StateType> {
           // Handle destroying infra we've provisioned
           switch (cluster.service) {
             case "eks":
-              api.destroyEKS(
-                "<token>",
-                { eks_name: cluster.name },
-                {
-                  project_id: currentProject.id,
-                  infra_id: cluster.infra_id,
-                },
-                (err: any, res: any) => {
-                  if (err) {
-                    console.log(err);
-                  } else {
-                    console.log(
-                      "destroyed provisioned infra:",
-                      cluster.infra_id
-                    );
+              api
+                .destroyEKS(
+                  "<token>",
+                  { eks_name: cluster.name },
+                  {
+                    project_id: currentProject.id,
+                    infra_id: cluster.infra_id,
                   }
-                }
-              );
+                )
+                .then(() =>
+                  console.log("destroyed provisioned infra:", cluster.infra_id)
+                )
+                .catch(console.log);
               break;
 
             case "gke":
-              api.destroyGKE(
-                "<token>",
-                { gke_name: cluster.name },
-                {
-                  project_id: currentProject.id,
-                  infra_id: cluster.infra_id,
-                },
-                (err: any, res: any) => {
-                  if (err) {
-                    console.log(err);
-                  } else {
-                    console.log("destroyed provisioned infra.");
+              api
+                .destroyGKE(
+                  "<token>",
+                  { gke_name: cluster.name },
+                  {
+                    project_id: currentProject.id,
+                    infra_id: cluster.infra_id,
                   }
-                }
-              );
+                )
+                .then(() =>
+                  console.log("destroyed provisioned infra:", cluster.infra_id)
+                )
+                .catch(console.log);
               break;
 
             case "doks":
-              api.destroyDOKS(
-                "<token>",
-                { doks_name: cluster.name },
-                {
-                  project_id: currentProject.id,
-                  infra_id: cluster.infra_id,
-                },
-                (err: any, res: any) => {
-                  if (err) {
-                    console.log(err);
-                  } else {
-                    console.log("destroyed provisioned infra.");
+              api
+                .destroyDOKS(
+                  "<token>",
+                  { doks_name: cluster.name },
+                  {
+                    project_id: currentProject.id,
+                    infra_id: cluster.infra_id,
                   }
-                }
-              );
+                )
+                .then(() =>
+                  console.log("destroyed provisioned infra:", cluster.infra_id)
+                )
+                .catch(console.log);
               break;
           }
         }
-      }
-    );
+      })
+      .catch(console.log);
     setCurrentModal(null, null);
     this.props.history.push("dashboard?tab=overview");
   };

+ 16 - 13
dashboard/src/main/home/cluster-dashboard/NamespaceSelector.tsx

@@ -26,17 +26,16 @@ export default class NamespaceSelector extends Component<PropsType, StateType> {
   updateOptions = () => {
     let { currentCluster, currentProject } = this.context;
 
-    api.getNamespaces(
-      "<token>",
-      {
-        cluster_id: currentCluster.id,
-      },
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err && this._isMounted) {
-          // setCurrentError('Could not read clusters: ' + JSON.stringify(err));
-          this.setState({ namespaceOptions: [{ label: "All", value: "" }] });
-        } else if (this._isMounted) {
+    api
+      .getNamespaces(
+        "<token>",
+        {
+          cluster_id: currentCluster.id,
+        },
+        { id: currentProject.id }
+      )
+      .then((res) => {
+        if (this._isMounted) {
           let namespaceOptions: { label: string; value: string }[] = [
             { label: "All", value: "" },
           ];
@@ -50,8 +49,12 @@ export default class NamespaceSelector extends Component<PropsType, StateType> {
           );
           this.setState({ namespaceOptions });
         }
-      }
-    );
+      })
+      .catch((err) => {
+        if (this._isMounted) {
+          this.setState({ namespaceOptions: [{ label: "All", value: "" }] });
+        }
+      });
   };
 
   componentDidMount() {

+ 68 - 68
dashboard/src/main/home/cluster-dashboard/chart/ChartList.tsx

@@ -34,61 +34,60 @@ export default class ChartList extends Component<PropsType, StateType> {
     websockets: {} as Record<string, any>,
   };
 
+  // TODO: promisify
   updateCharts = (callback: Function) => {
     let { currentCluster, currentProject, setCurrentError } = this.context;
     this.setState({ loading: true });
 
-    api.getCharts(
-      "<token>",
-      {
-        namespace: this.props.namespace,
-        cluster_id: currentCluster.id,
-        storage: StorageType.Secret,
-        limit: 20,
-        skip: 0,
-        byDate: false,
-        statusFilter: [
-          "deployed",
-          "uninstalled",
-          "pending",
-          "pending_upgrade",
-          "pending_rollback",
-          "superseded",
-          "failed",
-        ],
-      },
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          setCurrentError(JSON.stringify(err));
-          this.setState({ loading: false, error: true });
-        } else {
-          let charts = res.data || [];
-          if (this.props.sortType == "Newest") {
-            charts.sort((a: any, b: any) =>
-              Date.parse(a.info.last_deployed) >
-              Date.parse(b.info.last_deployed)
-                ? -1
-                : 1
-            );
-          } else if (this.props.sortType == "Oldest") {
-            charts.sort((a: any, b: any) =>
-              Date.parse(a.info.last_deployed) >
-              Date.parse(b.info.last_deployed)
-                ? 1
-                : -1
-            );
-          } else if (this.props.sortType == "Alphabetical") {
-            charts.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
-          }
-          this.setState({ charts }, () => {
-            this.setState({ loading: false, error: false });
-          });
-          callback(charts);
+    api
+      .getCharts(
+        "<token>",
+        {
+          namespace: this.props.namespace,
+          cluster_id: currentCluster.id,
+          storage: StorageType.Secret,
+          limit: 20,
+          skip: 0,
+          byDate: false,
+          statusFilter: [
+            "deployed",
+            "uninstalled",
+            "pending",
+            "pending_upgrade",
+            "pending_rollback",
+            "superseded",
+            "failed",
+          ],
+        },
+        { id: currentProject.id }
+      )
+      .then((res) => {
+        let charts = res.data || [];
+        if (this.props.sortType == "Newest") {
+          charts.sort((a: any, b: any) =>
+            Date.parse(a.info.last_deployed) > Date.parse(b.info.last_deployed)
+              ? -1
+              : 1
+          );
+        } else if (this.props.sortType == "Oldest") {
+          charts.sort((a: any, b: any) =>
+            Date.parse(a.info.last_deployed) > Date.parse(b.info.last_deployed)
+              ? 1
+              : -1
+          );
+        } else if (this.props.sortType == "Alphabetical") {
+          charts.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
         }
-      }
-    );
+        this.setState({ charts }, () => {
+          this.setState({ loading: false, error: false });
+        });
+        callback(charts);
+      })
+      .catch((err) => {
+        console.log(err);
+        setCurrentError(JSON.stringify(err));
+        this.setState({ loading: false, error: true });
+      });
   };
 
   setupWebsocket = (kind: string) => {
@@ -150,23 +149,21 @@ export default class ChartList extends Component<PropsType, StateType> {
       if (chart.info.status == "failed") return;
 
       await new Promise((next: (res?: any) => void) => {
-        api.getChartControllers(
-          "<token>",
-          {
-            namespace: chart.namespace,
-            cluster_id: currentCluster.id,
-            storage: StorageType.Secret,
-          },
-          {
-            id: currentProject.id,
-            name: chart.name,
-            revision: chart.version,
-          },
-          (err: any, res: any) => {
-            if (err) {
-              setCurrentError(JSON.stringify(err));
-              return;
+        api
+          .getChartControllers(
+            "<token>",
+            {
+              namespace: chart.namespace,
+              cluster_id: currentCluster.id,
+              storage: StorageType.Secret,
+            },
+            {
+              id: currentProject.id,
+              name: chart.name,
+              revision: chart.version,
             }
+          )
+          .then((res) => {
             // transform controller array into hash table for easy lookup during updates.
             let chartControllers = {} as Record<string, Record<string, any>>;
             res.data.forEach((c: any) => {
@@ -194,8 +191,11 @@ export default class ChartList extends Component<PropsType, StateType> {
               });
             });
             next();
-          }
-        );
+          })
+          .catch((err) => {
+            setCurrentError(JSON.stringify(err));
+            return;
+          });
       });
     });
   };

+ 123 - 139
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -80,27 +80,25 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     let { currentCluster, currentChart, setCurrentChart } = this.props;
 
     this.setState({ loading: true });
-    api.getChart(
-      "<token>",
-      {
-        namespace: currentChart.namespace,
-        cluster_id: currentCluster.id,
-        storage: StorageType.Secret,
-      },
-      {
-        name: chart.name,
-        revision: chart.version,
-        id: currentProject.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          setCurrentChart(res.data);
-          this.setState({ loading: false });
+    api
+      .getChart(
+        "<token>",
+        {
+          namespace: currentChart.namespace,
+          cluster_id: currentCluster.id,
+          storage: StorageType.Secret,
+        },
+        {
+          name: chart.name,
+          revision: chart.version,
+          id: currentProject.id,
         }
-      }
-    );
+      )
+      .then((res) => {
+        setCurrentChart(res.data);
+        this.setState({ loading: false });
+      })
+      .catch(console.log);
   };
 
   getControllers = async (chart: ChartType) => {
@@ -108,26 +106,23 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
 
     // don't retrieve controllers for chart that failed to even deploy.
     if (chart.info.status == "failed") return;
-
+    // TODO: properly promisify
     await new Promise((next: (res?: any) => void) => {
-      api.getChartControllers(
-        "<token>",
-        {
-          namespace: chart.namespace,
-          cluster_id: currentCluster.id,
-          storage: StorageType.Secret,
-        },
-        {
-          id: currentProject.id,
-          name: chart.name,
-          revision: chart.version,
-        },
-        (err: any, res: any) => {
-          if (err) {
-            setCurrentError(JSON.stringify(err));
-            return;
+      api
+        .getChartControllers(
+          "<token>",
+          {
+            namespace: chart.namespace,
+            cluster_id: currentCluster.id,
+            storage: StorageType.Secret,
+          },
+          {
+            id: currentProject.id,
+            name: chart.name,
+            revision: chart.version,
           }
-
+        )
+        .then((res) => {
           res.data.forEach(async (c: any) => {
             await new Promise((nextController: (res?: any) => void) => {
               c.metadata.kind = c.kind;
@@ -145,8 +140,8 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
             });
           });
           next();
-        }
-      );
+        })
+        .catch((err) => setCurrentError(JSON.stringify(err)));
     });
   };
 
@@ -198,29 +193,27 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     let { currentCluster, currentProject } = this.context;
     let { currentChart } = this.props;
 
-    api.getChartComponents(
-      "<token>",
-      {
-        namespace: currentChart.namespace,
-        cluster_id: currentCluster.id,
-        storage: StorageType.Secret,
-      },
-      {
-        id: currentProject.id,
-        name: currentChart.name,
-        revision: currentChart.version,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          this.setState({
-            components: res.data.Objects,
-            podSelectors: res.data.PodSelectors,
-          });
+    api
+      .getChartComponents(
+        "<token>",
+        {
+          namespace: currentChart.namespace,
+          cluster_id: currentCluster.id,
+          storage: StorageType.Secret,
+        },
+        {
+          id: currentProject.id,
+          name: currentChart.name,
+          revision: currentChart.version,
         }
-      }
-    );
+      )
+      .then((res) => {
+        this.setState({
+          components: res.data.Objects,
+          podSelectors: res.data.PodSelectors,
+        });
+      })
+      .catch(console.log);
   };
 
   refreshChart = () => this.getChartData(this.props.currentChart);
@@ -242,30 +235,30 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
 
     this.setState({ saveValuesStatus: "loading" });
     this.refreshChart();
-    api.upgradeChartValues(
-      "<token>",
-      {
-        namespace: this.props.currentChart.namespace,
-        storage: StorageType.Secret,
-        values: valuesYaml,
-      },
-      {
-        id: currentProject.id,
-        name: this.props.currentChart.name,
-        cluster_id: currentCluster.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          this.setState({ saveValuesStatus: "error" });
-          console.log(err);
-        } else {
-          this.setState({
-            saveValuesStatus: "successful",
-            forceRefreshRevisions: true,
-          });
+    api
+      .upgradeChartValues(
+        "<token>",
+        {
+          namespace: this.props.currentChart.namespace,
+          storage: StorageType.Secret,
+          values: valuesYaml,
+        },
+        {
+          id: currentProject.id,
+          name: this.props.currentChart.name,
+          cluster_id: currentCluster.id,
         }
-      }
-    );
+      )
+      .then((res) => {
+        this.setState({
+          saveValuesStatus: "successful",
+          forceRefreshRevisions: true,
+        });
+      })
+      .catch((err) => {
+        this.setState({ saveValuesStatus: "error" });
+        console.log(err);
+      });
   };
 
   renderTabContents = () => {
@@ -489,43 +482,36 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
       this.props.currentChart
     );
 
-    api.getChartComponents(
-      "<token>",
-      {
-        namespace: currentChart.namespace,
-        cluster_id: currentCluster.id,
-        storage: StorageType.Secret,
-      },
-      {
-        id: currentProject.id,
-        name: currentChart.name,
-        revision: currentChart.version,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          this.setState({ components: res.data.Objects });
+    api
+      .getChartComponents(
+        "<token>",
+        {
+          namespace: currentChart.namespace,
+          cluster_id: currentCluster.id,
+          storage: StorageType.Secret,
+        },
+        {
+          id: currentProject.id,
+          name: currentChart.name,
+          revision: currentChart.version,
         }
-      }
-    );
+      )
+      .then((res) => this.setState({ components: res.data.Objects }))
+      .catch(console.log);
 
-    api.getIngress(
-      "<token>",
-      {
-        cluster_id: currentCluster.id,
-      },
-      {
-        id: currentProject.id,
-        name: `${this.props.currentChart.name}-docker`,
-        namespace: `${this.props.currentChart.namespace}`,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          return;
+    api
+      .getIngress(
+        "<token>",
+        {
+          cluster_id: currentCluster.id,
+        },
+        {
+          id: currentProject.id,
+          name: `${this.props.currentChart.name}-docker`,
+          namespace: `${this.props.currentChart.namespace}`,
         }
-
+      )
+      .then((res) => {
         if (res.data?.spec?.rules && res.data?.spec?.rules[0]?.host) {
           this.setState({ url: `https://${res.data?.spec?.rules[0]?.host}` });
           return;
@@ -537,8 +523,8 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
           });
           return;
         }
-      }
-    );
+      })
+      .catch(console.log);
 
     this.updateTabs();
   }
@@ -590,25 +576,23 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     let { currentProject, currentCluster } = this.context;
     let { currentChart } = this.props;
     this.setState({ deleting: true });
-    api.uninstallTemplate(
-      "<token>",
-      {},
-      {
-        namespace: currentChart.namespace,
-        storage: StorageType.Secret,
-        name: currentChart.name,
-        id: currentProject.id,
-        cluster_id: currentCluster.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          this.setState({ showDeleteOverlay: false });
-          this.props.setCurrentChart(null);
+    api
+      .uninstallTemplate(
+        "<token>",
+        {},
+        {
+          namespace: currentChart.namespace,
+          storage: StorageType.Secret,
+          name: currentChart.name,
+          id: currentProject.id,
+          cluster_id: currentCluster.id,
         }
-      }
-    );
+      )
+      .then((res) => {
+        this.setState({ showDeleteOverlay: false });
+        this.props.setCurrentChart(null);
+      })
+      .catch(console.log);
   };
 
   renderDeleteOverlay = () => {

+ 46 - 49
dashboard/src/main/home/cluster-dashboard/expanded-chart/RevisionSection.tsx

@@ -35,32 +35,29 @@ export default class RevisionSection extends Component<PropsType, StateType> {
     maxVersion: 0, // Track most recent version even when previewing old revisions
   };
 
-  refreshHistory = (callback?: () => void) => {
+  refreshHistory = () => {
     let { chart } = this.props;
     let { currentCluster, currentProject } = this.context;
-    api.getRevisions(
-      "<token>",
-      {
-        namespace: chart.namespace,
-        cluster_id: currentCluster.id,
-        storage: StorageType.Secret,
-      },
-      { id: currentProject.id, name: chart.name },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          res.data.sort((a: ChartType, b: ChartType) => {
-            return -(a.version - b.version);
-          });
-          this.setState({
-            revisions: res.data,
-            maxVersion: res.data[0].version,
-          });
-          callback && callback();
-        }
-      }
-    );
+    return api
+      .getRevisions(
+        "<token>",
+        {
+          namespace: chart.namespace,
+          cluster_id: currentCluster.id,
+          storage: StorageType.Secret,
+        },
+        { id: currentProject.id, name: chart.name }
+      )
+      .then((res) => {
+        res.data.sort((a: ChartType, b: ChartType) => {
+          return -(a.version - b.version);
+        });
+        this.setState({
+          revisions: res.data,
+          maxVersion: res.data[0].version,
+        });
+      })
+      .catch(console.log);
   };
 
   componentDidMount() {
@@ -73,7 +70,7 @@ export default class RevisionSection extends Component<PropsType, StateType> {
       this.props.refreshRevisionsOff();
 
       // Force refresh occurs on submit -> set current to newest
-      this.refreshHistory(() => {
+      this.refreshHistory().then(() => {
         this.props.setRevision(this.state.revisions[0], true);
       });
     } else if (this.props.chart !== prevProps.chart) {
@@ -97,31 +94,31 @@ export default class RevisionSection extends Component<PropsType, StateType> {
     let revisionNumber = this.state.rollbackRevision;
     this.setState({ loading: true, rollbackRevision: null });
 
-    api.rollbackChart(
-      "<token>",
-      {
-        namespace: this.props.chart.namespace,
-        storage: StorageType.Secret,
-        revision: revisionNumber,
-      },
-      {
-        id: currentProject.id,
-        name: this.props.chart.name,
-        cluster_id: currentCluster.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          setCurrentError(err.response.data);
-          this.setState({ loading: false });
-        } else {
-          this.setState({ loading: false });
-          this.refreshHistory(() => {
-            this.props.setRevision(this.state.revisions[0], true);
-          });
+    api
+      .rollbackChart(
+        "<token>",
+        {
+          namespace: this.props.chart.namespace,
+          storage: StorageType.Secret,
+          revision: revisionNumber,
+        },
+        {
+          id: currentProject.id,
+          name: this.props.chart.name,
+          cluster_id: currentCluster.id,
         }
-      }
-    );
+      )
+      .then((res) => {
+        this.setState({ loading: false });
+        this.refreshHistory().then(() => {
+          this.props.setRevision(this.state.revisions[0], true);
+        });
+      })
+      .catch((err) => {
+        console.log(err);
+        setCurrentError(err.response.data);
+        this.setState({ loading: false });
+      });
   };
 
   handleClickRevision = (revision: ChartType) => {

+ 79 - 66
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -25,31 +25,31 @@ type PropsType = {
 };
 
 type StateType = {
-  actionConfig: ActionConfigType,
-  sourceType: string,
-  selectedImageUrl: string | null,
-  selectedTag: string | null,
-  saveValuesStatus: string | null,
-  values: string,
-  webhookToken: string,
-  highlightCopyButton: boolean,
+  actionConfig: ActionConfigType;
+  sourceType: string;
+  selectedImageUrl: string | null;
+  selectedTag: string | null;
+  saveValuesStatus: string | null;
+  values: string;
+  webhookToken: string;
+  highlightCopyButton: boolean;
   action: ActionConfigType;
 };
 
 export default class SettingsSection extends Component<PropsType, StateType> {
   state = {
     actionConfig: {
-      git_repo: '',
-      image_repo_uri: '',
+      git_repo: "",
+      image_repo_uri: "",
       git_repo_id: 0,
-      dockerfile_path: '',
+      dockerfile_path: "",
     } as ActionConfigType,
-    sourceType: '',
-    selectedImageUrl: '',
-    selectedTag: '',
-    values: '',
-    saveValuesStatus: null as (string | null),
-    webhookToken: '',
+    sourceType: "",
+    selectedImageUrl: "",
+    selectedTag: "",
+    values: "",
+    saveValuesStatus: null as string | null,
+    webhookToken: "",
     highlightCopyButton: false,
     action: {
       git_repo: "",
@@ -69,18 +69,24 @@ export default class SettingsSection extends Component<PropsType, StateType> {
       selectedTag: image?.tag,
     });
 
-    api.getReleaseToken('<token>', {
-      namespace: this.props.currentChart.namespace,
-      cluster_id: currentCluster.id,
-      storage: StorageType.Secret
-    }, { id: currentProject.id, name: this.props.currentChart.name }, (err: any, res: any) => {
-      if (err) {
-        console.log(err);
-      } else {
+    api
+      .getReleaseToken(
+        "<token>",
+        {
+          namespace: this.props.currentChart.namespace,
+          cluster_id: currentCluster.id,
+          storage: StorageType.Secret,
+        },
+        { id: currentProject.id, name: this.props.currentChart.name }
+      )
+      .then((res) => {
         console.log(res.data);
-        this.setState({ action: res.data.git_action_config, webhookToken: res.data.webhook_token });
-      }
-    });
+        this.setState({
+          action: res.data.git_action_config,
+          webhookToken: res.data.webhook_token,
+        });
+      })
+      .catch(console.log);
   }
 
   redeployWithNewImage = (img: string, tag: string) => {
@@ -103,28 +109,28 @@ export default class SettingsSection extends Component<PropsType, StateType> {
     };
 
     let values = yaml.dump(image);
-    api.upgradeChartValues(
-      "<token>",
-      {
-        namespace: this.props.currentChart.namespace,
-        storage: StorageType.Secret,
-        values,
-      },
-      {
-        id: currentProject.id,
-        name: this.props.currentChart.name,
-        cluster_id: currentCluster.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          this.setState({ saveValuesStatus: "error" });
-        } else {
-          this.setState({ saveValuesStatus: "successful" });
-          this.props.refreshChart();
+    api
+      .upgradeChartValues(
+        "<token>",
+        {
+          namespace: this.props.currentChart.namespace,
+          storage: StorageType.Secret,
+          values,
+        },
+        {
+          id: currentProject.id,
+          name: this.props.currentChart.name,
+          cluster_id: currentCluster.id,
         }
-      }
-    );
+      )
+      .then((res) => {
+        this.setState({ saveValuesStatus: "successful" });
+        this.props.refreshChart();
+      })
+      .catch((err) => {
+        console.log(err);
+        this.setState({ saveValuesStatus: "error" });
+      });
   };
 
   /*
@@ -143,34 +149,34 @@ export default class SettingsSection extends Component<PropsType, StateType> {
           <Holder>
             <InputRow
               disabled={true}
-              label='Git Repository'
-              type='text'
-              width='100%'
+              label="Git Repository"
+              type="text"
+              width="100%"
               value={this.state.action.git_repo}
               setValue={(x: string) => console.log(x)}
             />
             <InputRow
               disabled={true}
-              label='Dockerfile Path'
-              type='text'
-              width='100%'
+              label="Dockerfile Path"
+              type="text"
+              width="100%"
               value={this.state.action.dockerfile_path}
               setValue={(x: string) => console.log(x)}
             />
             <InputRow
               disabled={true}
-              label='Docker Image Repository'
-              type='text'
-              width='100%'
+              label="Docker Image Repository"
+              type="text"
+              width="100%"
               value={this.state.action.image_repo_uri}
               setValue={(x: string) => console.log(x)}
             />
           </Holder>
         </>
-      )
+      );
     }
 
-    if (this.state.sourceType === 'registry') {
+    if (this.state.sourceType === "registry") {
       return (
         <>
           <Heading>Connected Source</Heading>
@@ -193,19 +199,26 @@ export default class SettingsSection extends Component<PropsType, StateType> {
       <>
         <Heading>Connect a Source</Heading>
         <Helper>
-          Select a repo to connect to. You can 
-          <A padRight={true} href={`/api/oauth/projects/${currentProject.id}/github?redirected=true`}>
+          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' })}>
+          </A>{" "}
+          or
+          <Highlight onClick={() => this.setState({ sourceType: "registry" })}>
             link an image registry
-          </Highlight>.
+          </Highlight>
+          .
         </Helper>
         <RepoSelector
           chart={this.props.currentChart}
           forceExpanded={true}
           actionConfig={this.state.actionConfig}
-          setActionConfig={(actionConfig: ActionConfigType) => this.setState({ actionConfig })}
+          setActionConfig={(actionConfig: ActionConfigType) =>
+            this.setState({ actionConfig })
+          }
         />
       </>
     );

+ 21 - 21
dashboard/src/main/home/cluster-dashboard/expanded-chart/ValuesYaml.tsx

@@ -48,28 +48,28 @@ export default class ValuesYaml extends Component<PropsType, StateType> {
     let { currentCluster, setCurrentError, currentProject } = this.context;
     this.setState({ saveValuesStatus: "loading" });
 
-    api.upgradeChartValues(
-      "<token>",
-      {
-        namespace: this.props.currentChart.namespace,
-        storage: StorageType.Secret,
-        values: this.state.values,
-      },
-      {
-        id: currentProject.id,
-        name: this.props.currentChart.name,
-        cluster_id: currentCluster.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          this.setState({ saveValuesStatus: "error" });
-        } else {
-          this.setState({ saveValuesStatus: "successful" });
-          this.props.refreshChart();
+    api
+      .upgradeChartValues(
+        "<token>",
+        {
+          namespace: this.props.currentChart.namespace,
+          storage: StorageType.Secret,
+          values: this.state.values,
+        },
+        {
+          id: currentProject.id,
+          name: this.props.currentChart.name,
+          cluster_id: currentCluster.id,
         }
-      }
-    );
+      )
+      .then((res) => {
+        this.setState({ saveValuesStatus: "successful" });
+        this.props.refreshChart();
+      })
+      .catch((err) => {
+        console.log(err);
+        this.setState({ saveValuesStatus: "error" });
+      });
   };
 
   render() {

+ 17 - 16
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx

@@ -45,21 +45,18 @@ export default class ControllerTab extends Component<PropsType, StateType> {
     }
     selectors.push(selector);
 
-    api.getMatchingPods(
-      "<token>",
-      {
-        cluster_id: currentCluster.id,
-        selectors,
-      },
-      {
-        id: currentProject.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          setCurrentError(JSON.stringify(err));
-          return;
+    api
+      .getMatchingPods(
+        "<token>",
+        {
+          cluster_id: currentCluster.id,
+          selectors,
+        },
+        {
+          id: currentProject.id,
         }
+      )
+      .then((res) => {
         let pods = res?.data?.map((pod: any) => {
           return {
             namespace: pod?.metadata?.namespace,
@@ -77,8 +74,12 @@ export default class ControllerTab extends Component<PropsType, StateType> {
         if (isFirst) {
           selectPod(res.data[0]);
         }
-      }
-    );
+      })
+      .catch((err) => {
+        console.log(err);
+        setCurrentError(JSON.stringify(err));
+        return;
+      });
   }
 
   getAvailability = (kind: string, c: any) => {

+ 26 - 25
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx

@@ -75,42 +75,43 @@ export default class StatusSection extends Component<PropsType, StateType> {
           <TabWrapper>{this.renderTabs()}</TabWrapper>
           {this.renderLogs()}
         </Wrapper>
-      )
+      );
     }
 
     return (
-      <NoControllers> 
-        <i className="material-icons">category</i> 
-        No objects to display. This might happen while your app is still deploying.
+      <NoControllers>
+        <i className="material-icons">category</i>
+        No objects to display. This might happen while your app is still
+        deploying.
       </NoControllers>
-    )
-  }
+    );
+  };
 
   componentDidMount() {
     const { selectors, currentChart } = this.props;
     let { currentCluster, currentProject, setCurrentError } = this.context;
 
-    api.getChartControllers(
-      "<token>",
-      {
-        namespace: currentChart.namespace,
-        cluster_id: currentCluster.id,
-        storage: StorageType.Secret,
-      },
-      {
-        id: currentProject.id,
-        name: currentChart.name,
-        revision: currentChart.version,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          setCurrentError(JSON.stringify(err));
-          this.setState({ controllers: [], loading: false });
-          return;
+    api
+      .getChartControllers(
+        "<token>",
+        {
+          namespace: currentChart.namespace,
+          cluster_id: currentCluster.id,
+          storage: StorageType.Secret,
+        },
+        {
+          id: currentProject.id,
+          name: currentChart.name,
+          revision: currentChart.version,
         }
+      )
+      .then((res) => {
         this.setState({ controllers: res.data, loading: false });
-      }
-    );
+      })
+      .catch((err) => {
+        setCurrentError(JSON.stringify(err));
+        this.setState({ controllers: [], loading: false });
+      });
   }
 
   render() {

+ 8 - 12
dashboard/src/main/home/dashboard/ClusterList.tsx

@@ -25,18 +25,16 @@ class Templates extends Component<PropsType, StateType> {
   };
 
   componentDidMount() {
-    api.getClusters(
-      "<token>",
-      {},
-      { id: this.context.currentProject.id },
-      (err: any, res: any) => {
-        if (res && res.data) {
+    api
+      .getClusters("<token>", {}, { id: this.context.currentProject.id })
+      .then((res) => {
+        if (res.data) {
           this.setState({ clusters: res.data, loading: false, error: "" });
         } else {
-          this.setState({ loading: false, error: err });
+          this.setState({ loading: false, error: "Response data missing" });
         }
-      }
-    );
+      })
+      .catch((err) => this.setState(err));
   }
 
   renderIcon = () => {
@@ -68,9 +66,7 @@ class Templates extends Component<PropsType, StateType> {
     return (
       <StyledClusterList>
         <Helper>Clusters connected to this project:</Helper>
-        <TemplateList>
-          {this.renderClusters()}
-        </TemplateList>
+        <TemplateList>{this.renderClusters()}</TemplateList>
       </StyledClusterList>
     );
   }

+ 9 - 13
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -36,20 +36,16 @@ class Dashboard extends Component<PropsType, StateType> {
 
   refreshInfras = () => {
     if (this.props.projectId) {
-      api.getInfra(
-        "<token>",
-        {},
-        {
-          project_id: this.props.projectId,
-        },
-        (err: any, res: any) => {
-          if (err) {
-            console.log(err);
-            return;
+      api
+        .getInfra(
+          "<token>",
+          {},
+          {
+            project_id: this.props.projectId,
           }
-          this.setState({ infras: res.data });
-        }
-      );
+        )
+        .then((res) => this.setState({ infras: res.data }))
+        .catch(console.log);
     }
   };
 

+ 99 - 91
dashboard/src/main/home/integrations/Integrations.tsx

@@ -1,26 +1,24 @@
 import React, { Component } from "react";
 import styled from "styled-components";
 
-import { Context } from 'shared/Context';
-import api from 'shared/api';
-import { integrationList } from 'shared/common';
-import { ActionConfigType, ChoiceType } from 'shared/types';
+import { Context } from "shared/Context";
+import api from "shared/api";
+import { integrationList } from "shared/common";
 
 import IntegrationList from "./IntegrationList";
 import IntegrationForm from "./integration-form/IntegrationForm";
 
-import GHIcon from 'assets/GithubIcon';
+import GHIcon from "assets/GithubIcon";
 
-type PropsType = {
-};
+type PropsType = {};
 
 type StateType = {
-  currentCategory: string | null,
-  currentIntegration: string | null,
-  currentOptions: any[],
-  currentTitles: any[],
-  currentIds: any[],
-  currentIntegrationData: any[],
+  currentCategory: string | null;
+  currentIntegration: string | null;
+  currentOptions: any[];
+  currentTitles: any[];
+  currentIds: any[];
+  currentIntegrationData: any[];
 };
 
 export default class Integrations extends Component<PropsType, StateType> {
@@ -43,65 +41,49 @@ export default class Integrations extends Component<PropsType, StateType> {
     });
     switch (categoryType) {
       case "kubernetes":
-        api.getProjectClusters(
-          "<token>",
-          {},
-          { id: currentProject.id },
-          (err: any, res: any) => {
-            if (err) {
-              console.log(err);
-            } else {
-              // console.log(res.data)
-            }
-          }
-        );
+        api
+          .getProjectClusters("<token>", {}, { id: currentProject.id })
+          .then()
+          .catch(console.log);
         break;
       case "registry":
-        api.getProjectRegistries(
-          "<token>",
-          {},
-          { id: currentProject.id },
-          (err: any, res: any) => {
-            if (err) {
-              console.log(err);
-            } else {
-              // Sort res.data into service type and sort each service's registry alphabetically
-              let grouped: any = {};
-              let final: any = [];
-              for (let i = 0; i < res.data.length; i++) {
-                let p = res.data[i].service;
-                if (!grouped[p]) {
-                  grouped[p] = [];
-                }
-                grouped[p].push(res.data[i]);
+        api
+          .getProjectRegistries("<token>", {}, { id: currentProject.id })
+          .then((res) => {
+            // Sort res.data into service type and sort each service's registry alphabetically
+            let grouped: any = {};
+            let final: any = [];
+            for (let i = 0; i < res.data.length; i++) {
+              let p = res.data[i].service;
+              if (!grouped[p]) {
+                grouped[p] = [];
               }
-              Object.values(grouped).forEach((val: any) => {
-                final = final.concat(
-                  val.sort((a: any, b: any) => (a.name > b.name ? 1 : -1))
-                );
-              });
-
-              let currentOptions = [] as string[];
-              let currentTitles = [] as string[];
-              final.forEach((integration: any, i: number) => {
-                currentOptions.push(integration.service);
-                currentTitles.push(integration.name);
-              });
-              this.setState({
-                currentOptions,
-                currentTitles,
-                currentIntegrationData: res.data,
-              });
+              grouped[p].push(res.data[i]);
             }
-          }
-        );
+            Object.values(grouped).forEach((val: any) => {
+              final = final.concat(
+                val.sort((a: any, b: any) => (a.name > b.name ? 1 : -1))
+              );
+            });
+
+            let currentOptions = [] as string[];
+            let currentTitles = [] as string[];
+            final.forEach((integration: any, i: number) => {
+              currentOptions.push(integration.service);
+              currentTitles.push(integration.name);
+            });
+            this.setState({
+              currentOptions,
+              currentTitles,
+              currentIntegrationData: res.data,
+            });
+          })
+          .catch(console.log);
         break;
-      case 'repo':
-        api.getGitRepos('<token>', {
-        }, { project_id: currentProject.id }, (err: any, res: any) => {
-          if (err) {
-            console.log(err);
-          } else {
+      case "repo":
+        api
+          .getGitRepos("<token>", {}, { project_id: currentProject.id })
+          .then((res) => {
             let currentOptions = [] as string[];
             let currentTitles = [] as string[];
             let currentIds = [] as any[];
@@ -109,10 +91,15 @@ export default class Integrations extends Component<PropsType, StateType> {
               currentOptions.push(item.service);
               currentTitles.push(item.repo_entity);
               currentIds.push(item.id);
-            })
-            this.setState({ currentOptions, currentTitles, currentIds, currentIntegrationData: res.data })
-          }
-        });
+            });
+            this.setState({
+              currentOptions,
+              currentTitles,
+              currentIds,
+              currentIntegrationData: res.data,
+            });
+          })
+          .catch(console.log);
         break;
       default:
         console.log("Unknown integration category.");
@@ -187,38 +174,52 @@ export default class Integrations extends Component<PropsType, StateType> {
         </div>
       );
     } else if (currentCategory) {
-      let icon = integrationList[currentCategory] && integrationList[currentCategory].icon;
-      let label = integrationList[currentCategory] && integrationList[currentCategory].label;
-      let buttonText = integrationList[currentCategory] && integrationList[currentCategory].buttonText;
-      if (currentCategory !== 'repo') {
+      let icon =
+        integrationList[currentCategory] &&
+        integrationList[currentCategory].icon;
+      let label =
+        integrationList[currentCategory] &&
+        integrationList[currentCategory].label;
+      let buttonText =
+        integrationList[currentCategory] &&
+        integrationList[currentCategory].buttonText;
+      if (currentCategory !== "repo") {
         return (
           <div>
             <TitleSectionAlt>
               <Flex>
-                <i className="material-icons" onClick={() => this.setState({ currentCategory: null })}>
+                <i
+                  className="material-icons"
+                  onClick={() => this.setState({ currentCategory: null })}
+                >
                   keyboard_backspace
                 </i>
                 <Icon src={icon && icon} />
                 <Title>{label}</Title>
               </Flex>
-              <Button 
-                onClick={() => this.context.setCurrentModal('IntegrationsModal', { 
-                  category: currentCategory,
-                  setCurrentIntegration: (x: string) => this.setState({ currentIntegration: x })
-                })}
+              <Button
+                onClick={() =>
+                  this.context.setCurrentModal("IntegrationsModal", {
+                    category: currentCategory,
+                    setCurrentIntegration: (x: string) =>
+                      this.setState({ currentIntegration: x }),
+                  })
+                }
               >
                 <i className="material-icons">add</i>
                 {buttonText}
               </Button>
             </TitleSectionAlt>
-  
+
             <LineBreak />
-  
+
             <IntegrationList
               currentCategory={currentCategory}
               integrations={this.state.currentOptions}
               titles={this.state.currentTitles}
-              setCurrent={(x: string) => this.setState({ currentIntegration: x })}
+              setCurrent={(x: string) =>
+                this.setState({ currentIntegration: x })
+              }
               itemIdentifier={this.state.currentIntegrationData}
             />
           </div>
@@ -228,27 +229,34 @@ export default class Integrations extends Component<PropsType, StateType> {
           <div>
             <TitleSectionAlt>
               <Flex>
-                <i className="material-icons" onClick={() => this.setState({ currentCategory: null })}>
+                <i
+                  className="material-icons"
+                  onClick={() => this.setState({ currentCategory: null })}
+                >
                   keyboard_backspace
                 </i>
                 <Icon src={icon && icon} />
                 <Title>{label}</Title>
               </Flex>
-              <Button 
-                onClick={() => window.open(`/api/oauth/projects/${currentProject.id}/github`)}
+              <Button
+                onClick={() =>
+                  window.open(`/api/oauth/projects/${currentProject.id}/github`)
+                }
               >
                 <GHIcon />
                 {buttonText}
               </Button>
             </TitleSectionAlt>
-  
+
             <LineBreak />
 
             <IntegrationList
               currentCategory={currentCategory}
               integrations={this.state.currentOptions}
               titles={this.state.currentTitles}
-              setCurrent={(x: string) => this.setState({ currentIntegration: x })}
+              setCurrent={(x: string) =>
+                this.setState({ currentIntegration: x })
+              }
               itemIdentifier={this.state.currentIds}
             />
           </div>
@@ -262,8 +270,8 @@ export default class Integrations extends Component<PropsType, StateType> {
         </TitleSection>
 
         <IntegrationList
-          currentCategory={''}
-          integrations={['kubernetes', 'registry', 'repo']}
+          currentCategory={""}
+          integrations={["kubernetes", "registry", "repo"]}
           setCurrent={(x: any) => this.setState({ currentCategory: x })}
           isCategory={true}
         />

+ 24 - 20
dashboard/src/main/home/integrations/integration-form/ECRForm.tsx

@@ -41,30 +41,34 @@ export default class ECRForm extends Component<PropsType, StateType> {
     return false;
   };
 
+  catchErr = (err: any) => console.log(err);
+
   handleSubmit = () => {
     let { awsRegion, awsAccessId, awsSecretKey, credentialsName } = this.state;
     let { currentProject } = this.context;
 
-    api.createAWSIntegration('<token>', {
-      aws_region: awsRegion,
-      aws_access_key_id: awsAccessId,
-      aws_secret_access_key: awsSecretKey,
-    }, { id: currentProject.id }, (err: any, res: any) => {
-      if (err) {
-        console.log(err);
-      } else {
-        api.connectECRRegistry('<token>', {
-          name: credentialsName,
-          aws_integration_id: res.data.id,
-        }, { id: currentProject.id }, (err: any, res: any) => {
-          if (err) {
-            console.log(err);
-          } else {
-            this.props.closeForm();
-          }
-        });
-      }
-    });
+    api
+      .createAWSIntegration(
+        "<token>",
+        {
+          aws_region: awsRegion,
+          aws_access_key_id: awsAccessId,
+          aws_secret_access_key: awsSecretKey,
+        },
+        { id: currentProject.id }
+      )
+      .then((res) =>
+        api.connectECRRegistry(
+          "<token>",
+          {
+            name: credentialsName,
+            aws_integration_id: res.data.id,
+          },
+          { id: currentProject.id }
+        )
+      )
+      .then(() => this.props.closeForm())
+      .catch(this.catchErr);
   };
 
   render() {

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

@@ -15,59 +15,75 @@ type PropsType = {
 };
 
 type StateType = {
-  credentialsName: string,
-  gcpRegion: string,
-  serviceAccountKey: string,
-  gcpProjectID: string,
-  url: string,
+  credentialsName: string;
+  gcpRegion: string;
+  serviceAccountKey: string;
+  gcpProjectID: string;
+  url: string;
 };
 
 export default class GCRForm extends Component<PropsType, StateType> {
   state = {
-    credentialsName: '',
-    gcpRegion: '',
-    serviceAccountKey: '',
-    gcpProjectID: '',
-    url: '',
-  }
+    credentialsName: "",
+    gcpRegion: "",
+    serviceAccountKey: "",
+    gcpProjectID: "",
+    url: "",
+  };
 
   isDisabled = (): boolean => {
-    let { credentialsName, gcpRegion, gcpProjectID, serviceAccountKey } = this.state;
-    if (credentialsName === '' || gcpRegion  === '' || serviceAccountKey === '' || gcpProjectID === '') {
+    let {
+      credentialsName,
+      gcpRegion,
+      gcpProjectID,
+      serviceAccountKey,
+    } = this.state;
+    if (
+      credentialsName === "" ||
+      gcpRegion === "" ||
+      serviceAccountKey === "" ||
+      gcpProjectID === ""
+    ) {
       return true;
     }
     return false;
   };
 
+  catchError = (err: any) => console.log(err);
+
   handleSubmit = () => {
     let { currentProject } = this.context;
 
-    api.createGCPIntegration('<token>', {
-      gcp_region: this.state.gcpRegion,
-      gcp_key_data: this.state.serviceAccountKey,
-      gcp_project_id: this.state.gcpProjectID,
-    }, {
-      project_id: currentProject.id,
-    }, (err: any, res: any) => {
-      if (err) {
-        console.log(err);
-      } else {
-        api.connectGCRRegistry('<token>', {
-          name: this.state.credentialsName,
-          gcp_integration_id: res.data.id,
-          url: this.state.url,
-        }, {
-          id: currentProject.id,
-        }, (err: any, res: any) => {
-          if (err) {
-            console.log(err);
-          } else {
-            console.log(res.data);
-            this.props.closeForm();
+    api
+      .createGCPIntegration(
+        "<token>",
+        {
+          gcp_region: this.state.gcpRegion,
+          gcp_key_data: this.state.serviceAccountKey,
+          gcp_project_id: this.state.gcpProjectID,
+        },
+        {
+          project_id: currentProject.id,
+        }
+      )
+      .then((res) =>
+        api.connectGCRRegistry(
+          "<token>",
+          {
+            name: this.state.credentialsName,
+            gcp_integration_id: res.data.id,
+            url: this.state.url,
+          },
+          {
+            id: currentProject.id,
           }
-        })
-      }
-    });
+        )
+      )
+      .then((res) => {
+        console.log(res.data);
+        this.props.closeForm();
+      })
+      .catch(this.catchError);
   };
 
   render() {
@@ -81,10 +97,12 @@ export default class GCRForm extends Component<PropsType, StateType> {
           <InputRow
             type="text"
             value={this.state.credentialsName}
-            setValue={(credentialsName: string) => this.setState({ credentialsName })}
-            label='🏷️ Registry Name'
-            placeholder='ex: paper-straw'
-            width='100%'
+            setValue={(credentialsName: string) =>
+              this.setState({ credentialsName })
+            }
+            label="🏷️ Registry Name"
+            placeholder="ex: paper-straw"
+            width="100%"
           />
           <Heading>GCP Settings</Heading>
           <Helper>Service account credentials for GCP permissions.</Helper>
@@ -92,32 +110,34 @@ export default class GCRForm extends Component<PropsType, StateType> {
             type="text"
             value={this.state.gcpRegion}
             setValue={(gcpRegion: string) => this.setState({ gcpRegion })}
-            label='📍 GCP Region'
-            placeholder='ex: uranus-north3'
-            width='100%'
+            label="📍 GCP Region"
+            placeholder="ex: uranus-north3"
+            width="100%"
           />
           <TextArea
             value={this.state.serviceAccountKey}
-            setValue={(serviceAccountKey: string) => this.setState({ serviceAccountKey })}
-            label='🔑 Service Account Key (JSON)'
-            placeholder='(Paste your JSON service account key here)'
-            width='100%'
+            setValue={(serviceAccountKey: string) =>
+              this.setState({ serviceAccountKey })
+            }
+            label="🔑 Service Account Key (JSON)"
+            placeholder="(Paste your JSON service account key here)"
+            width="100%"
           />
           <InputRow
             type="text"
             value={this.state.gcpProjectID}
             setValue={(gcpProjectID: string) => this.setState({ gcpProjectID })}
-            label='📝 GCP Project ID'
-            placeholder='ex: skynet-dev-172969'
-            width='100%'
+            label="📝 GCP Project ID"
+            placeholder="ex: skynet-dev-172969"
+            width="100%"
           />
           <InputRow
-            type='text'
+            type="text"
             value={this.state.url}
             setValue={(url: string) => this.setState({ url })}
-            label='🔗 GCR URL'
-            placeholder='ex: gcr.io/skynet-dev-172969'
-            width='100%'
+            label="🔗 GCR URL"
+            placeholder="ex: gcr.io/skynet-dev-172969"
+            width="100%"
           />
         </CredentialWrapper>
         <SaveButton

+ 17 - 24
dashboard/src/main/home/modals/IntegrationsModal.tsx

@@ -20,30 +20,20 @@ export default class IntegrationsModal extends Component<PropsType, StateType> {
   componentDidMount() {
     let { category } = this.context.currentModalData;
     if (category === "kubernetes") {
-      api.getClusterIntegrations("<token>", {}, {}, (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          this.setState({ integrations: res.data });
-        }
-      });
+      api
+        .getClusterIntegrations("<token>", {}, {})
+        .then((res) => this.setState({ integrations: res.data }))
+        .catch((err) => console.log(err));
     } else if (category === "registry") {
-      api.getRegistryIntegrations("<token>", {}, {}, (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          // console.log(res.data)
-          this.setState({ integrations: res.data });
-        }
-      });
+      api
+        .getRegistryIntegrations("<token>", {}, {})
+        .then((res) => this.setState({ integrations: res.data }))
+        .catch((err) => console.log(err));
     } else {
-      api.getRepoIntegrations("<token>", {}, {}, (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          this.setState({ integrations: res.data });
-        }
-      });
+      api
+        .getRepoIntegrations("<token>", {}, {})
+        .then((res) => this.setState({ integrations: res.data }))
+        .catch((err) => console.log(err));
     }
   }
 
@@ -51,8 +41,11 @@ export default class IntegrationsModal extends Component<PropsType, StateType> {
     if (this.context.currentModalData) {
       let { setCurrentIntegration } = this.context.currentModalData;
       return this.state.integrations.map((integration: any, i: number) => {
-        let icon = integrationList[integration.service] && integrationList[integration.service].icon;
-        let disabled = integration.service === 'kube' || integration.service === 'docker';
+        let icon =
+          integrationList[integration.service] &&
+          integrationList[integration.service].icon;
+        let disabled =
+          integration.service === "kube" || integration.service === "docker";
         return (
           <IntegrationOption
             key={i}

+ 46 - 61
dashboard/src/main/home/modals/UpdateClusterModal.tsx

@@ -28,24 +28,25 @@ class UpdateClusterModal extends Component<PropsType, StateType> {
     showDeleteOverlay: false,
   };
 
+  catchErr = (err: any) => {
+    this.setState({ status: "error" });
+    console.log(err);
+  };
+
   handleDelete = () => {
     let { currentProject, currentCluster } = this.context;
     this.setState({ status: "loading" });
 
-    api.deleteCluster(
-      "<token>",
-      {},
-      {
-        project_id: currentProject.id,
-        cluster_id: currentCluster.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          this.setState({ status: "error" });
-          console.log(err);
-          return;
+    api
+      .deleteCluster(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
         }
-
+      )
+      .then((_) => {
         if (!currentCluster?.infra_id) {
           // TODO: make this more declarative from the Home component
           this.props.setRefreshClusters(true);
@@ -58,68 +59,52 @@ class UpdateClusterModal extends Component<PropsType, StateType> {
         // Handle destroying infra we've provisioned
         switch (currentCluster.service) {
           case "eks":
-            api.destroyEKS(
-              "<token>",
-              { eks_name: currentCluster.name },
-              {
-                project_id: currentProject.id,
-                infra_id: currentCluster.infra_id,
-              },
-              (err: any, res: any) => {
-                if (err) {
-                  this.setState({ status: "error" });
-                  console.log(err);
-                } else {
-                  console.log("destroyed provisioned infra.");
+            api
+              .destroyEKS(
+                "<token>",
+                { eks_name: currentCluster.name },
+                {
+                  project_id: currentProject.id,
+                  infra_id: currentCluster.infra_id,
                 }
-              }
-            );
+              )
+              .then(() => console.log("destroyed provisioned infra."))
+              .catch(this.catchErr);
             break;
-
           case "gke":
-            api.destroyGKE(
-              "<token>",
-              { gke_name: currentCluster.name },
-              {
-                project_id: currentProject.id,
-                infra_id: currentCluster.infra_id,
-              },
-              (err: any, res: any) => {
-                if (err) {
-                  this.setState({ status: "error" });
-                  console.log(err);
-                } else {
-                  console.log("destroyed provisioned infra.");
+            api
+              .destroyGKE(
+                "<token>",
+                { gke_name: currentCluster.name },
+                {
+                  project_id: currentProject.id,
+                  infra_id: currentCluster.infra_id,
                 }
-              }
-            );
+              )
+              .then(() => console.log("destroyed provisioned infra."))
+              .catch(this.catchErr);
             break;
 
           case "doks":
-            api.destroyDOKS(
-              "<token>",
-              { doks_name: currentCluster.name },
-              {
-                project_id: currentProject.id,
-                infra_id: currentCluster.infra_id,
-              },
-              (err: any, res: any) => {
-                if (err) {
-                  this.setState({ status: "error" });
-                  console.log(err);
-                } else {
-                  console.log("destroyed provisioned infra.");
+            api
+              .destroyDOKS(
+                "<token>",
+                { doks_name: currentCluster.name },
+                {
+                  project_id: currentProject.id,
+                  infra_id: currentCluster.infra_id,
                 }
-              }
-            );
+              )
+              .then(() => console.log("destroyed provisioned infra."))
+              .catch(this.catchErr);
             break;
         }
 
         this.props.setRefreshClusters(true);
         this.setState({ status: "successful", showDeleteOverlay: false });
         this.context.setCurrentModal(null, null);
-      }
-    );
+      })
+      .catch(this.catchErr);
   };
 
   renderWarning = () => {

+ 4 - 3
dashboard/src/main/home/navbar/Navbar.tsx

@@ -25,9 +25,10 @@ export default class Navbar extends Component<PropsType, StateType> {
     let { setCurrentError } = this.context;
 
     // Attempt user logout
-    api.logOutUser("<token>", {}, {}, (err: any, res: any) => {
-      err ? setCurrentError(err.response.data.errors[0]) : logOut();
-    });
+    api
+      .logOutUser("<token>", {}, {})
+      .then(logOut)
+      .catch((err) => setCurrentError(err.response.data.errors[0]));
   };
 
   renderSettingsDropdown = () => {

+ 48 - 66
dashboard/src/main/home/project-settings/InviteList.tsx

@@ -39,20 +39,16 @@ export default class InviteList extends Component<PropsType, StateType> {
     let { currentProject } = this.context;
 
     this.setState({ loading: true });
-    api.getInvites(
-      "<token>",
-      {},
-      {
-        id: currentProject.id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          this.setState({ invites: res.data, loading: false });
+    api
+      .getInvites(
+        "<token>",
+        {},
+        {
+          id: currentProject.id,
         }
-      }
-    );
+      )
+      .then((res) => this.setState({ invites: res.data, loading: false }))
+      .catch((err) => console.log(err));
   };
 
   validateEmail = () => {
@@ -67,68 +63,54 @@ export default class InviteList extends Component<PropsType, StateType> {
 
   createInvite = () => {
     let { currentProject } = this.context;
-    api.createInvite(
-      "<token>",
-      { email: this.state.email },
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          this.getInviteData();
-          this.setState({ email: "" });
-        }
-      }
-    );
+    api
+      .createInvite(
+        "<token>",
+        { email: this.state.email },
+        { id: currentProject.id }
+      )
+      .then((_) => {
+        this.getInviteData();
+        this.setState({ email: "" });
+      })
+      .catch((err) => console.log(err));
   };
 
   deleteInvite = (index: number) => {
     let { currentProject } = this.context;
-    api.deleteInvite(
-      "<token>",
-      {},
-      {
-        id: currentProject.id,
-        invId: this.state.invites[index].id,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          this.getInviteData();
+    api
+      .deleteInvite(
+        "<token>",
+        {},
+        {
+          id: currentProject.id,
+          invId: this.state.invites[index].id,
         }
-      }
-    );
+      )
+      .then(this.getInviteData)
+      .catch((err) => console.log(err));
   };
 
   replaceInvite = (index: number) => {
     let { currentProject } = this.context;
-    api.createInvite(
-      "<token>",
-      { email: this.state.invites[index].email },
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          api.deleteInvite(
-            "<token>",
-            {},
-            {
-              id: currentProject.id,
-              invId: this.state.invites[index].id,
-            },
-            (err: any, res: any) => {
-              if (err) {
-                console.log(err);
-              } else {
-                this.getInviteData();
-              }
-            }
-          );
-        }
-      }
-    );
+    api
+      .createInvite(
+        "<token>",
+        { email: this.state.invites[index].email },
+        { id: currentProject.id }
+      )
+      .then((_) =>
+        api.deleteInvite(
+          "<token>",
+          {},
+          {
+            id: currentProject.id,
+            invId: this.state.invites[index].id,
+          }
+        )
+      )
+      .then(this.getInviteData)
+      .catch((err) => console.log(err));
   };
 
   copyToClip = (index: number) => {

+ 67 - 93
dashboard/src/main/home/provisioner/AWSFormSection.tsx

@@ -104,130 +104,100 @@ class AWSFormSection extends Component<PropsType, StateType> {
     }
   };
 
+  catchError = (err: any) => {
+    console.log(err);
+    this.props.handleError();
+  };
+
   // Step 1: Create a project
+  // TODO: promisify this function
   createProject = (callback?: any) => {
     console.log("Creating project");
     let { projectName, handleError } = this.props;
     let { user, setProjects, setCurrentProject, currentProject } = this.context;
 
-    api.createProject(
-      "<token>",
-      { name: projectName },
-      {},
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          handleError();
-          return;
-        } else {
-          let proj = res.data;
-
-          // Need to set project list for dropdown
-          // TODO: consolidate into ProjectSection (case on exists in list on set)
-          api.getProjects(
+    api
+      .createProject("<token>", { name: projectName }, {})
+      .then((res) => {
+        let proj = res.data;
+        // Need to set project list for dropdown
+        // TODO: consolidate into ProjectSection (case on exists in list on set)
+        api
+          .getProjects(
             "<token>",
             {},
             {
               id: user.userId,
-            },
-            (err: any, res: any) => {
-              if (err) {
-                console.log(err);
-                handleError();
-                return;
-              }
-              setProjects(res.data);
-              setCurrentProject(proj, () => {
-                callback && callback();
-              });
             }
-          );
-        }
-      }
-    );
+          )
+          .then((res) => {
+            setProjects(res.data);
+            setCurrentProject(proj, () => {
+              callback && callback();
+            });
+          })
+          .catch(this.catchError);
+      })
+      .catch(this.catchError);
   };
 
-  provisionECR = (callback?: any) => {
+  provisionECR = () => {
     console.log("Provisioning ECR");
     let { awsAccessId, awsSecretKey, awsRegion } = this.state;
     let { currentProject } = this.context;
-    let { handleError } = this.props;
-
-    api.createAWSIntegration(
-      "<token>",
-      {
-        aws_region: awsRegion,
-        aws_access_key_id: awsAccessId,
-        aws_secret_access_key: awsSecretKey,
-      },
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          handleError();
-          return;
-        }
 
+    return api
+      .createAWSIntegration(
+        "<token>",
+        {
+          aws_region: awsRegion,
+          aws_access_key_id: awsAccessId,
+          aws_secret_access_key: awsSecretKey,
+        },
+        { id: currentProject.id }
+      )
+      .then((res) =>
         api.provisionECR(
           "<token>",
           {
             aws_integration_id: res.data.id,
             ecr_name: `${currentProject.name}-registry`,
           },
-          { id: currentProject.id },
-          (err: any, res: any) => {
-            if (err) {
-              console.log(err);
-              handleError();
-              return;
-            }
-            callback && callback();
-          }
-        );
-      }
-    );
+          { id: currentProject.id }
+        )
+      )
+      .catch(this.catchError);
   };
 
   provisionEKS = () => {
     console.log("Provisioning EKS");
-    let { handleError } = this.props;
     let { awsAccessId, awsSecretKey, awsRegion } = this.state;
     let { currentProject } = this.context;
 
     let clusterName = `${currentProject.name}-cluster`;
-    api.createAWSIntegration(
-      "<token>",
-      {
-        aws_region: awsRegion,
-        aws_access_key_id: awsAccessId,
-        aws_secret_access_key: awsSecretKey,
-        aws_cluster_id: clusterName,
-      },
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          handleError();
-          return;
-        }
+    api
+      .createAWSIntegration(
+        "<token>",
+        {
+          aws_region: awsRegion,
+          aws_access_key_id: awsAccessId,
+          aws_secret_access_key: awsSecretKey,
+          aws_cluster_id: clusterName,
+        },
+        { id: currentProject.id }
+      )
+      .then((res) =>
         api.provisionEKS(
           "<token>",
           {
             aws_integration_id: res.data.id,
             eks_name: clusterName,
           },
-          { id: currentProject.id },
-          (err: any, eks: any) => {
-            if (err) {
-              console.log(err);
-              handleError();
-              return;
-            }
-            this.props.history.push("dashboard?tab=provisioner");
-          }
-        );
-      }
-    );
+          { id: currentProject.id }
+        )
+      )
+      .then(() => this.props.history.push("dashboard?tab=provisioner"))
+      .catch(this.catchError);
   };
 
   // TODO: handle generically (with > 2 steps)
@@ -238,10 +208,12 @@ class AWSFormSection extends Component<PropsType, StateType> {
     if (!projectName) {
       if (selectedInfras.length === 2) {
         // Case: project exists, provision ECR + EKS
-        this.provisionECR(this.provisionEKS);
+        this.provisionECR().then(this.provisionEKS);
       } else if (selectedInfras[0].value === "ecr") {
         // Case: project exists, only provision ECR
-        this.provisionECR(() => this.props.history.push("dashboard?tab=provisioner"));
+        this.provisionECR().then(() =>
+          this.props.history.push("dashboard?tab=provisioner")
+        );
       } else {
         // Case: project exists, only provision EKS
         this.provisionEKS();
@@ -249,12 +221,14 @@ class AWSFormSection extends Component<PropsType, StateType> {
     } else {
       if (selectedInfras.length === 2) {
         // Case: project DNE, provision ECR + EKS
-        this.createProject(() => this.provisionECR(this.provisionEKS));
+        this.createProject(() => this.provisionECR().then(this.provisionEKS));
       } else if (selectedInfras[0].value === "ecr") {
         // Case: project DNE, only provision ECR
-        this.createProject(() => this.provisionECR(() => {
-          this.props.history.push("dashboard?tab=provisioner");
-        }));
+        this.createProject(() =>
+          this.provisionECR().then(() => {
+            this.props.history.push("dashboard?tab=provisioner");
+          })
+        );
       } else {
         // Case: project DNE, only provision EKS
         this.createProject(this.provisionEKS);

+ 25 - 34
dashboard/src/main/home/provisioner/DOFormSection.tsx

@@ -87,46 +87,37 @@ export default class DOFormSection extends Component<PropsType, StateType> {
     }
   };
 
+  catchError = (err: any) => {
+    console.log(err);
+    this.props.handleError();
+    return;
+  };
+
   // Step 1: Create a project
   createProject = (callback?: any) => {
     console.log("Creating project");
-    let { projectName, handleError } = this.props;
+    let { projectName } = this.props;
     let { user, setProjects, setCurrentProject } = this.context;
 
-    api.createProject(
-      "<token>",
-      { name: projectName },
-      {},
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          handleError();
-          return;
-        } else {
-          let proj = res.data;
+    api
+      .createProject("<token>", { name: projectName }, {})
+      .then(async (res) => {
+        let proj = res.data;
 
-          // Need to set project list for dropdown
-          // TODO: consolidate into ProjectSection (case on exists in list on set)
-          api.getProjects(
-            "<token>",
-            {},
-            {
-              id: user.userId,
-            },
-            (err: any, res: any) => {
-              if (err) {
-                console.log(err);
-                handleError();
-                return;
-              }
-              setProjects(res.data);
-              setCurrentProject(proj);
-              callback && callback(proj.id);
-            }
-          );
-        }
-      }
-    );
+        // Need to set project list for dropdown
+        // TODO: consolidate into ProjectSection (case on exists in list on set)
+        const res_1 = await api.getProjects(
+          "<token>",
+          {},
+          {
+            id: user.userId,
+          }
+        );
+        setProjects(res_1.data);
+        setCurrentProject(proj);
+        callback && callback(proj.id);
+      })
+      .catch(this.catchError);
   };
 
   doRedirect = (projectId: number) => {

+ 23 - 32
dashboard/src/main/home/provisioner/ExistingClusterSection.tsx

@@ -27,39 +27,30 @@ class ExistingClusterSection extends Component<PropsType, StateType> {
     let { user, setProjects, setCurrentProject } = this.context;
 
     this.setState({ buttonStatus: "loading" });
-    api.createProject(
-      "<token>",
-      { name: projectName },
-      {},
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          api.getProjects(
-            "<token>",
-            {},
-            {
-              id: user.userId,
-            },
-            (err: any, res: any) => {
-              if (err) {
-                console.log(err);
-              } else if (res.data) {
-                setProjects(res.data);
-                if (res.data.length > 0) {
-                  let proj = res.data.find((el: ProjectType) => {
-                    return el.name === projectName;
-                  });
-                  setCurrentProject(proj);
-
-                  this.props.history.push("dashboard?tab=overview");
-                }
-              }
-            }
-          );
+    api
+      .createProject("<token>", { name: projectName }, {})
+      .then((res) =>
+        api.getProjects(
+          "<token>",
+          {},
+          {
+            id: user.userId,
+          }
+        )
+      )
+      .then((res) => {
+        if (res.data) {
+          setProjects(res.data);
+          if (res.data.length > 0) {
+            let proj = res.data.find((el: ProjectType) => {
+              return el.name === projectName;
+            });
+            setCurrentProject(proj);
+            this.props.history.push("dashboard?tab=overview");
+          }
         }
-      }
-    );
+      })
+      .catch(console.log);
   };
 
   render() {

+ 74 - 82
dashboard/src/main/home/provisioner/GCPFormSection.tsx

@@ -107,68 +107,55 @@ class GCPFormSection extends Component<PropsType, StateType> {
     }
   };
 
+  catchError = (err: any) => {
+    console.log(err);
+    this.props.handleError();
+  };
+
   // Step 1: Create a project
   createProject = (callback?: any) => {
     console.log("Creating project");
     let { projectName, handleError } = this.props;
     let { user, setProjects, setCurrentProject } = this.context;
 
-    api.createProject(
-      "<token>",
-      { name: projectName },
-      {},
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          handleError();
-          return;
-        } else {
-          let proj = res.data;
-
-          // Need to set project list for dropdown
-          // TODO: consolidate into ProjectSection (case on exists in list on set)
-          api.getProjects(
+    api
+      .createProject("<token>", { name: projectName }, {})
+      .then((res) => {
+        let proj = res.data;
+
+        // Need to set project list for dropdown
+        // TODO: consolidate into ProjectSection (case on exists in list on set)
+        api
+          .getProjects(
             "<token>",
             {},
             {
               id: user.userId,
-            },
-            (err: any, res: any) => {
-              if (err) {
-                console.log(err);
-                handleError();
-                return;
-              }
-              setProjects(res.data);
-              setCurrentProject(proj);
-              callback && callback();
             }
-          );
-        }
-      }
-    );
+          )
+          .then((res) => {
+            setProjects(res.data);
+            setCurrentProject(proj);
+            callback && callback();
+          })
+          .catch(this.catchError);
+      })
+      .catch(this.catchError);
   };
 
   provisionGCR = (id: number, callback?: any) => {
     console.log("Provisioning GCR");
     let { currentProject } = this.context;
-    let { handleError } = this.props;
 
-    api.createGCR(
-      "<token>",
-      {
-        gcp_integration_id: id,
-      },
-      { project_id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          handleError();
-          return;
-        }
-        callback && callback();
-      }
-    );
+    return api
+      .createGCR(
+        "<token>",
+        {
+          gcp_integration_id: id,
+        },
+        { project_id: currentProject.id }
+      )
+      .catch(this.catchError);
   };
 
   provisionGKE = (id: number) => {
@@ -177,49 +164,54 @@ class GCPFormSection extends Component<PropsType, StateType> {
     let { currentProject } = this.context;
 
     let clusterName = `${currentProject.name}-cluster`;
-    api.createGKE(
-      "<token>",
-      {
-        gke_name: clusterName,
-        gcp_integration_id: id,
-      },
-      { project_id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-          handleError();
-          return;
-        }
+    api
+      .createGKE(
+        "<token>",
+        {
+          gke_name: clusterName,
+          gcp_integration_id: id,
+        },
+        { project_id: currentProject.id }
+      )
+      .then((res) => {
         this.props.history.push("dashboard?tab=provisioner");
-    });
-  }
+      })
+      .catch(this.catchError);
+  };
 
   handleCreateFlow = () => {
     let { selectedInfras, gcpKeyData, gcpProjectId, gcpRegion } = this.state;
     let { currentProject } = this.context;
-    api.createGCPIntegration('<token>', {
-      gcp_region: gcpRegion,
-      gcp_key_data: gcpKeyData,
-      gcp_project_id: gcpProjectId,
-    }, { project_id: currentProject.id }, (err: any, res: any) => {
-      if (err) {
-        console.log(err);
-      } else if (res?.data) {
-        console.log('gcp provisioned with response: ', res.data);
-        let { id } = res.data;
-
-        if (selectedInfras.length === 2) {
-          // Case: project exists, provision GCR + GKE
-          this.provisionGCR(id, () => this.provisionGKE(id));
-        } else if (selectedInfras[0].value === 'gcr') {
-          // Case: project exists, only provision GCR
-          this.provisionGCR(id, () => this.props.history.push("dashboard?tab=provisioner"));
-        } else {
-          // Case: project exists, only provision GKE
-          this.provisionGKE(id);
+    api
+      .createGCPIntegration(
+        "<token>",
+        {
+          gcp_region: gcpRegion,
+          gcp_key_data: gcpKeyData,
+          gcp_project_id: gcpProjectId,
+        },
+        { project_id: currentProject.id }
+      )
+      .then((res) => {
+        if (res?.data) {
+          console.log("gcp provisioned with response: ", res.data);
+          let { id } = res.data;
+
+          if (selectedInfras.length === 2) {
+            // Case: project exists, provision GCR + GKE
+            this.provisionGCR(id).then(() => this.provisionGKE(id));
+          } else if (selectedInfras[0].value === "gcr") {
+            // Case: project exists, only provision GCR
+            this.provisionGCR(id).then(() =>
+              this.props.history.push("dashboard?tab=provisioner")
+            );
+          } else {
+            // Case: project exists, only provision GKE
+            this.provisionGKE(id);
+          }
         }
-      }
-    });
+      })
+      .catch(console.log);
   };
 
   // TODO: handle generically (with > 2 steps)

+ 54 - 53
dashboard/src/main/home/provisioner/Provisioner.tsx

@@ -1,39 +1,35 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
-
-import api from 'shared/api';
-import { Context } from 'shared/Context';
-import loading from 'assets/loading.gif';
-import warning from 'assets/warning.png';
-import { InfraType, ProjectType } from 'shared/types';
-import Loading from 'components/Loading';
-
-import Helper from 'components/values-form/Helper';
-import InfraStatuses from './InfraStatuses';
-import ProvisionerLogs from './ProvisionerLogs'
+import React, { Component } from "react";
+import styled from "styled-components";
+
+import api from "shared/api";
+import { Context } from "shared/Context";
+import { InfraType, ProjectType } from "shared/types";
+import Loading from "components/Loading";
+
+import InfraStatuses from "./InfraStatuses";
+import ProvisionerLogs from "./ProvisionerLogs";
 import { RouteComponentProps, withRouter } from "react-router";
-import { Link } from "react-router-dom";
 
 type PropsType = RouteComponentProps & {};
 
 type StateType = {
-  error: boolean,
-  logs: string[],
-  websockets: any[],
-  maxStep : Record<string, number>,
-  currentStep: Record<string, number>,
-  triggerEnd: boolean,
-  infras: InfraType[],
-  loading: boolean,
-  selectedInfra: InfraType,
-  currentProject: ProjectType,
+  error: boolean;
+  logs: string[];
+  websockets: any[];
+  maxStep: Record<string, number>;
+  currentStep: Record<string, number>;
+  triggerEnd: boolean;
+  infras: InfraType[];
+  loading: boolean;
+  selectedInfra: InfraType;
+  currentProject: ProjectType;
 };
 
 class Provisioner extends Component<PropsType, StateType> {
   state = {
     error: false,
     logs: [] as string[],
-    websockets : [] as any[],
+    websockets: [] as any[],
     maxStep: {} as Record<string, any>,
     currentStep: {} as Record<string, number>,
     triggerEnd: false,
@@ -41,59 +37,64 @@ class Provisioner extends Component<PropsType, StateType> {
     selectedInfra: null as InfraType,
     loading: true,
     currentProject: this.context.currentProject,
-  }
+  };
 
   selectInfra = (infra: InfraType) => {
-    this.setState({ selectedInfra: infra })
-  }
+    this.setState({ selectedInfra: infra });
+  };
 
   componentDidMount() {
     let { currentProject } = this.state;
 
-    api.getInfra('<token>', {}, { 
-      project_id: currentProject.id 
-    }, (err: any, res: any) => {
-      if (err) return;
-
-      let infras = res.data.sort((a: InfraType, b: InfraType) => {
-        return b.id - a.id
-      });
-
-      this.setState({ 
-        error: false, 
-        infras, 
-        loading: false,
-        selectedInfra: infras[0],
-      });
-    });
+    api
+      .getInfra(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+        }
+      )
+      .then((res) => {
+        let infras = res.data.sort((a: InfraType, b: InfraType) => {
+          return b.id - a.id;
+        });
+
+        this.setState({
+          error: false,
+          infras,
+          loading: false,
+          selectedInfra: infras[0],
+        });
+      })
+      .catch();
   }
 
   render() {
     if (this.state.loading) {
       return (
-        <StyledProvisioner> 
+        <StyledProvisioner>
           <Loading />
         </StyledProvisioner>
-      )
+      );
     }
 
     if (this.state.infras.length > 0) {
       return (
         <StyledProvisioner>
           <TabWrapper>
-            <InfraStatuses 
-              infras={this.state.infras} 
+            <InfraStatuses
+              infras={this.state.infras}
               selectInfra={this.selectInfra.bind(this)}
               selectedInfra={this.state.selectedInfra}
             />
           </TabWrapper>
 
-          <ProvisionerLogs 
-            key={this.state.selectedInfra?.id} 
-            selectedInfra={this.state.selectedInfra} 
+          <ProvisionerLogs
+            key={this.state.selectedInfra?.id}
+            selectedInfra={this.state.selectedInfra}
           />
         </StyledProvisioner>
-      )
+      );
     }
 
     return (
@@ -126,4 +127,4 @@ const TabWrapper = styled.div`
   min-width: 250px;
   height: 100%;
   overflow-y: auto;
-`;
+`;

+ 34 - 41
dashboard/src/main/home/sidebar/ClusterSection.tsx

@@ -41,52 +41,45 @@ class ClusterSection extends Component<PropsType, StateType> {
     let { currentProject, setCurrentCluster } = this.context;
 
     // TODO: query with selected filter once implemented
-    api.getClusters(
-      "<token>",
-      {},
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          // Assume intializing if no contexts
-          this.props.setWelcome(true);
-        } else {
-          this.props.setWelcome(false);
-          // TODO: handle uninitialized kubeconfig
-          if (res.data) {
-            let clusters = res.data;
-            clusters.sort((a: any, b: any) => a.id - b.id);
-            if (clusters.length > 0) {
-              this.setState({ clusters });
-              let saved = JSON.parse(
-                localStorage.getItem(currentProject.id + "-cluster")
-              );
-              if (saved !== "null") {
-                setCurrentCluster(clusters[0]);
-                for (let i = 0; i < clusters.length; i++) {
-                  if (
-                    clusters[i].id === saved.id &&
-                    clusters[i].project_id === saved.project_id &&
-                    clusters[i].name === saved.name
-                  ) {
-                    setCurrentCluster(clusters[i]);
-                    break;
-                  }
+    api
+      .getClusters("<token>", {}, { id: currentProject.id })
+      .then((res) => {
+        this.props.setWelcome(false);
+        // TODO: handle uninitialized kubeconfig
+        if (res.data) {
+          let clusters = res.data;
+          clusters.sort((a: any, b: any) => a.id - b.id);
+          if (clusters.length > 0) {
+            this.setState({ clusters });
+            let saved = JSON.parse(
+              localStorage.getItem(currentProject.id + "-cluster")
+            );
+            if (saved !== "null") {
+              setCurrentCluster(clusters[0]);
+              for (let i = 0; i < clusters.length; i++) {
+                if (
+                  clusters[i].id === saved.id &&
+                  clusters[i].project_id === saved.project_id &&
+                  clusters[i].name === saved.name
+                ) {
+                  setCurrentCluster(clusters[i]);
+                  break;
                 }
-              } else {
-                setCurrentCluster(clusters[0]);
               }
-            } else if (
-              this.props.currentView !== "provisioner" &&
-              this.props.currentView !== "new-project"
-            ) {
-              this.setState({ clusters: [] });
-              setCurrentCluster(null);
-              // this.props.history.push("dashboard?tab=overview");
+            } else {
+              setCurrentCluster(clusters[0]);
             }
+          } else if (
+            this.props.currentView !== "provisioner" &&
+            this.props.currentView !== "new-project"
+          ) {
+            this.setState({ clusters: [] });
+            setCurrentCluster(null);
+            // this.props.history.push("dashboard?tab=overview");
           }
         }
-      }
-    );
+      })
+      .catch((err) => this.props.setWelcome(true));
   };
 
   componentDidMount() {

+ 5 - 6
dashboard/src/main/home/templates/Templates.tsx

@@ -33,10 +33,9 @@ export default class Templates extends Component<PropsType, StateType> {
   };
 
   componentDidMount() {
-    api.getTemplates("<token>", {}, {}, (err: any, res: any) => {
-      if (err) {
-        this.setState({ loading: false, error: true });
-      } else {
+    api
+      .getTemplates("<token>", {}, {})
+      .then((res) => {
         this.setState({ porterTemplates: res.data, error: false }, () => {
           this.state.porterTemplates.sort((a, b) => (a.name > b.name ? 1 : -1));
           this.state.porterTemplates.sort((a, b) =>
@@ -44,8 +43,8 @@ export default class Templates extends Component<PropsType, StateType> {
           );
           this.setState({ loading: false });
         });
-      }
-    });
+      })
+      .catch(() => this.setState({ loading: false, error: true }));
   }
 
   renderIcon = (icon: string) => {

+ 21 - 23
dashboard/src/main/home/templates/expanded-template/ExpandedTemplate.tsx

@@ -36,30 +36,28 @@ export default class ExpandedTemplate extends Component<PropsType, StateType> {
 
   componentDidMount() {
     this.setState({ loading: true });
-    api.getTemplateInfo(
-      "<token>",
-      {},
-      {
-        name: this.props.currentTemplate.name.toLowerCase().trim(),
-        version: "latest",
-      },
-      (err: any, res: any) => {
-        if (err) {
-          this.setState({ loading: false, error: true });
-        } else {
-          let { form, values, markdown, metadata } = res.data;
-          let keywords = metadata.keywords;
-          this.setState({
-            form,
-            values,
-            markdown,
-            keywords,
-            loading: false,
-            error: false,
-          });
+    api
+      .getTemplateInfo(
+        "<token>",
+        {},
+        {
+          name: this.props.currentTemplate.name.toLowerCase().trim(),
+          version: "latest",
         }
-      }
-    );
+      )
+      .then((res) => {
+        let { form, values, markdown, metadata } = res.data;
+        let keywords = metadata.keywords;
+        this.setState({
+          form,
+          values,
+          markdown,
+          keywords,
+          loading: false,
+          error: false,
+        });
+      })
+      .catch((err) => this.setState({ loading: false, error: true }));
   }
 
   renderContents = () => {

+ 120 - 132
dashboard/src/main/home/templates/expanded-template/LaunchTemplate.tsx

@@ -79,29 +79,24 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
     let { currentProject, currentCluster } = this.context;
     let { actionConfig } = this.state;
 
-    api.createGHAction(
-      "<token>",
-      {
-        git_repo: actionConfig.git_repo,
-        image_repo_uri: actionConfig.image_repo_uri,
-        dockerfile_path: actionConfig.dockerfile_path,
-        git_repo_id: actionConfig.git_repo_id,
-      },
-      {
-        project_id: currentProject.id,
-        CLUSTER_ID: currentCluster.id,
-        RELEASE_NAME: chartName,
-        RELEASE_NAMESPACE: chartNamespace,
-      },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else {
-          // Exit to initial settings tab
-          console.log(res.data);
+    api
+      .createGHAction(
+        "<token>",
+        {
+          git_repo: actionConfig.git_repo,
+          image_repo_uri: actionConfig.image_repo_uri,
+          dockerfile_path: actionConfig.dockerfile_path,
+          git_repo_id: actionConfig.git_repo_id,
+        },
+        {
+          project_id: currentProject.id,
+          CLUSTER_ID: currentCluster.id,
+          RELEASE_NAME: chartName,
+          RELEASE_NAMESPACE: chartNamespace,
         }
-      }
-    );
+      )
+      .then((res) => console.log(res.data))
+      .catch(console.log);
   };
 
   onSubmitAddon = (wildcard?: any) => {
@@ -115,46 +110,46 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
       _.set(values, key, wildcard[key]);
     }
 
-    api.deployTemplate(
-      "<token>",
-      {
-        templateName: this.props.currentTemplate.name,
-        storage: StorageType.Secret,
-        formValues: values,
-        namespace: this.state.selectedNamespace,
-        name,
-      },
-      {
-        id: currentProject.id,
-        cluster_id: currentCluster.id,
-        name: this.props.currentTemplate.name.toLowerCase().trim(),
-        version: "latest",
-      },
-      (err: any, res: any) => {
-        if (err) {
-          this.setState({ saveValuesStatus: "error" });
-          posthog.capture("Failed to deploy template", {
-            name: this.props.currentTemplate.name,
-            namespace: this.state.selectedNamespace,
-            values: values,
-            error: err,
-          });
-        } else {
-          if (this.state.sourceType === "repo") {
-            this.createGHAction(name, this.state.selectedNamespace);
-          }
-          // this.props.setCurrentView('cluster-dashboard');
-          this.setState({ saveValuesStatus: "successful" }, () => {
-            // redirect to dashboard
-          });
-          posthog.capture("Deployed template", {
-            name: this.props.currentTemplate.name,
-            namespace: this.state.selectedNamespace,
-            values: values,
-          });
+    api
+      .deployTemplate(
+        "<token>",
+        {
+          templateName: this.props.currentTemplate.name,
+          storage: StorageType.Secret,
+          formValues: values,
+          namespace: this.state.selectedNamespace,
+          name,
+        },
+        {
+          id: currentProject.id,
+          cluster_id: currentCluster.id,
+          name: this.props.currentTemplate.name.toLowerCase().trim(),
+          version: "latest",
         }
-      }
-    );
+      )
+      .then((_) => {
+        if (this.state.sourceType === "repo") {
+          this.createGHAction(name, this.state.selectedNamespace);
+        }
+        // this.props.setCurrentView('cluster-dashboard');
+        this.setState({ saveValuesStatus: "successful" }, () => {
+          // redirect to dashboard
+        });
+        posthog.capture("Deployed template", {
+          name: this.props.currentTemplate.name,
+          namespace: this.state.selectedNamespace,
+          values: values,
+        });
+      })
+      .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,
+        });
+      });
   };
 
   onSubmit = (rawValues: any) => {
@@ -198,47 +193,47 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
       ${currentCluster.id}\n}
     `);
 
-    api.deployTemplate(
-      "<token>",
-      {
-        templateName: this.props.currentTemplate.name,
-        imageURL: this.state.selectedImageUrl,
-        storage: StorageType.Secret,
-        formValues: values,
-        namespace: this.state.selectedNamespace,
-        name,
-      },
-      {
-        id: currentProject.id,
-        cluster_id: currentCluster.id,
-        name: this.props.currentTemplate.name.toLowerCase().trim(),
-        version: "latest",
-      },
-      (err: any, res: any) => {
-        if (err) {
-          this.setState({ saveValuesStatus: "error" });
-          posthog.capture("Failed to deploy template", {
-            name: this.props.currentTemplate.name,
-            namespace: this.state.selectedNamespace,
-            values: values,
-            error: err,
-          });
-        } else {
-          if (this.state.sourceType === "repo") {
-            this.createGHAction(name, this.state.selectedNamespace);
-          }
-          // this.props.setCurrentView('cluster-dashboard');
-          this.setState({ saveValuesStatus: "successful" }, () => {
-            // redirect to dashboard with namespace
-          });
-          posthog.capture("Deployed template", {
-            name: this.props.currentTemplate.name,
-            namespace: this.state.selectedNamespace,
-            values: values,
-          });
+    api
+      .deployTemplate(
+        "<token>",
+        {
+          templateName: this.props.currentTemplate.name,
+          imageURL: this.state.selectedImageUrl,
+          storage: StorageType.Secret,
+          formValues: values,
+          namespace: this.state.selectedNamespace,
+          name,
+        },
+        {
+          id: currentProject.id,
+          cluster_id: currentCluster.id,
+          name: this.props.currentTemplate.name.toLowerCase().trim(),
+          version: "latest",
         }
-      }
-    );
+      )
+      .then((res) => {
+        if (this.state.sourceType === "repo") {
+          this.createGHAction(name, this.state.selectedNamespace);
+        }
+        // this.props.setCurrentView('cluster-dashboard');
+        this.setState({ saveValuesStatus: "successful" }, () => {
+          // redirect to dashboard with namespace
+        });
+        posthog.capture("Deployed template", {
+          name: this.props.currentTemplate.name,
+          namespace: this.state.selectedNamespace,
+          values: values,
+        });
+      })
+      .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,
+        });
+      });
   };
 
   renderTabContents = () => {
@@ -292,42 +287,35 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
 
     // TODO: query with selected filter once implemented
     let { currentProject, currentCluster } = this.context;
-    api.getClusters(
-      "<token>",
-      {},
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          // console.log(err)
-        } else if (res.data) {
-          let clusterOptions: { label: string; value: string }[] = [];
-          let clusterMap: { [clusterId: string]: ClusterType } = {};
-          res.data.forEach((cluster: ClusterType, i: number) => {
-            clusterOptions.push({ label: cluster.name, value: cluster.name });
-            clusterMap[cluster.name] = cluster;
-          });
-          if (res.data.length > 0) {
-            this.setState({ clusterOptions, clusterMap });
-          }
+    api.getClusters("<token>", {}, { id: currentProject.id }).then((res) => {
+      if (res.data) {
+        let clusterOptions: { label: string; value: string }[] = [];
+        let clusterMap: { [clusterId: string]: ClusterType } = {};
+        res.data.forEach((cluster: ClusterType, i: number) => {
+          clusterOptions.push({ label: cluster.name, value: cluster.name });
+          clusterMap[cluster.name] = cluster;
+        });
+        if (res.data.length > 0) {
+          this.setState({ clusterOptions, clusterMap });
         }
       }
-    );
+    });
 
     this.updateNamespaces(currentCluster.id);
   }
 
   updateNamespaces = (id: number) => {
     let { currentProject } = this.context;
-    api.getNamespaces(
-      "<token>",
-      {
-        cluster_id: id,
-      },
-      { id: currentProject.id },
-      (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else if (res.data) {
+    api
+      .getNamespaces(
+        "<token>",
+        {
+          cluster_id: id,
+        },
+        { id: currentProject.id }
+      )
+      .then((res) => {
+        if (res.data) {
           let namespaceOptions = res.data.items.map(
             (x: { metadata: { name: string } }) => {
               return { label: x.metadata.name, value: x.metadata.name };
@@ -337,8 +325,8 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
             this.setState({ namespaceOptions });
           }
         }
-      }
-    );
+      })
+      .catch(console.log);
   };
 
   setSelectedImageUrl = (x: string) => {
@@ -393,7 +381,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
           </Placeholder>
           <SaveButton
             text="Deploy"
-            onClick={() => this.onSubmitAddon()}
+            onClick={this.onSubmitAddon}
             status={this.state.saveValuesStatus}
             makeFlush={true}
           />

+ 18 - 51
dashboard/src/shared/baseApi.tsx

@@ -8,12 +8,7 @@ export const baseApi = <T extends {}, S = {}>(
   requestType: string,
   endpoint: ((pathParams: S) => string) | string
 ) => {
-  return (
-    token: string,
-    params: T,
-    pathParams: S,
-    callback?: (err: any, res: any) => void
-  ) => {
+  return (token: string, params: T, pathParams: S) => {
     // Generate endpoint literal
     let endpointString: ((pathParams: S) => string) | string;
     if (typeof endpoint === "string") {
@@ -24,54 +19,26 @@ export const baseApi = <T extends {}, S = {}>(
 
     // Handle request type (can refactor)
     if (requestType === "POST") {
-      axios
-        .post(endpointString, params, {
-          headers: {
-            Authorization: `Bearer ${token}`,
-          },
-        })
-        .then((res) => {
-          callback && callback(null, res);
-        })
-        .catch((err) => {
-          callback && callback(err, null);
-        });
+      return axios.post(endpointString, params, {
+        headers: {
+          Authorization: `Bearer ${token}`,
+        },
+      });
     } else if (requestType === "PUT") {
-      axios
-        .put(endpointString, params, {
-          headers: {
-            Authorization: `Bearer ${token}`,
-          },
-        })
-        .then((res) => {
-          callback && callback(null, res);
-        })
-        .catch((err) => {
-          callback && callback(err, null);
-        });
+      return axios.put(endpointString, params, {
+        headers: {
+          Authorization: `Bearer ${token}`,
+        },
+      });
     } else if (requestType === "DELETE") {
-      axios
-        .delete(endpointString, params)
-        .then((res) => {
-          callback && callback(null, res);
-        })
-        .catch((err) => {
-          callback && callback(err, null);
-        });
+      return axios.delete(endpointString, params);
     } else {
-      axios
-        .get(endpointString, {
-          params,
-          paramsSerializer: function (params) {
-            return qs.stringify(params, { arrayFormat: "repeat" });
-          },
-        })
-        .then((res) => {
-          callback && callback(null, res);
-        })
-        .catch((err) => {
-          callback && callback(err, null);
-        });
+      return axios.get(endpointString, {
+        params,
+        paramsSerializer: function (params) {
+          return qs.stringify(params, { arrayFormat: "repeat" });
+        },
+      });
     }
   };
 };

+ 3 - 1
server/api/git_repo_handler.go

@@ -71,7 +71,9 @@ func (app *App) HandleListRepos(w http.ResponseWriter, r *http.Request) {
 	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
 
 	// list all repositories for specified user
-	repos, _, err := client.Repositories.List(context.Background(), "", nil)
+	repos, _, err := client.Repositories.List(context.Background(), "", &github.RepositoryListOptions{
+		Sort: "updated",
+	})
 
 	if err != nil {
 		app.handleErrorInternal(err, w)