Sfoglia il codice sorgente

formatted all files except in home

Jo Chuang 5 anni fa
parent
commit
d967ad10fc

+ 6 - 9
dashboard/src/App.tsx

@@ -1,14 +1,11 @@
-import React, { Component } from 'react';
-import styled, { createGlobalStyle } from 'styled-components';
+import React, { Component } from "react";
 
-import { ContextProvider } from './shared/Context';
-import Main from './main/Main';
+import { ContextProvider } from "./shared/Context";
+import Main from "./main/Main";
 
-type PropsType = {
-};
+type PropsType = {};
 
-type StateType = {
-};
+type StateType = {};
 
 export default class App extends Component<PropsType, StateType> {
   render() {
@@ -18,4 +15,4 @@ export default class App extends Component<PropsType, StateType> {
       </ContextProvider>
     );
   }
-}
+}

+ 5 - 8
dashboard/src/index.tsx

@@ -1,14 +1,11 @@
 import * as React from "react";
 import * as ReactDOM from "react-dom";
 import App from "./App";
-import * as FullStory from '@fullstory/browser';
- 
-FullStory.init({ 
+import * as FullStory from "@fullstory/browser";
+
+FullStory.init({
   orgId: process.env.FULLSTORY_ORG_ID,
-  devMode: process.env.NODE_ENV == 'development'
+  devMode: process.env.NODE_ENV == "development",
 });
 
-ReactDOM.render(
-  <App />,
-  document.getElementById("output")
-);
+ReactDOM.render(<App />, document.getElementById("output"));

+ 23 - 19
dashboard/src/main/CurrentError.tsx

@@ -1,40 +1,42 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
-import close from 'assets/close.png';
+import React, { Component } from "react";
+import styled from "styled-components";
+import close from "assets/close.png";
 
-import { Context } from 'shared/Context';
+import { Context } from "shared/Context";
 
 type PropsType = {
-  currentError: string,
+  currentError: string;
 };
 
-type StateType = {
-};
+type StateType = {};
 
 export default class CurrentError extends Component<PropsType, StateType> {
   state = {
     expanded: false,
-  }
+  };
 
   componentDidUpdate(prevProps: PropsType) {
     if (
-      prevProps.currentError !== this.props.currentError
-      && this.props.currentError === 'Provisioning failed. Check your credentials and try again.'
+      prevProps.currentError !== this.props.currentError &&
+      this.props.currentError ===
+        "Provisioning failed. Check your credentials and try again."
     ) {
       this.setState({ expanded: true });
     }
   }
-  
+
   render() {
     if (this.props.currentError) {
       if (!this.state.expanded) {
         return (
           <StyledCurrentError onClick={() => this.setState({ expanded: true })}>
             <ErrorText>Error: {this.props.currentError}</ErrorText>
-            <CloseButton onClick={(e) => {
-              this.context.setCurrentError(null);
-              e.stopPropagation();
-            }}>
+            <CloseButton
+              onClick={(e) => {
+                this.context.setCurrentError(null);
+                e.stopPropagation();
+              }}
+            >
               <CloseButtonImg src={close} />
             </CloseButton>
           </StyledCurrentError>
@@ -95,7 +97,7 @@ const StyledCurrentError = styled.div`
   left: 100px;
   padding: 15px;
   padding-right: 0px;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   height: 50px;
   font-size: 13px;
   border-radius: 3px;
@@ -115,10 +117,12 @@ const StyledCurrentError = styled.div`
 
   @keyframes floatIn {
     from {
-      opacity: 0; transform: translateY(20px);
+      opacity: 0;
+      transform: translateY(20px);
     }
     to {
-      opacity: 1; transform: translateY(0px);
+      opacity: 1;
+      transform: translateY(0px);
     }
   }
 `;
@@ -129,4 +133,4 @@ const ExpandedError = styled(StyledCurrentError)`
   max-height: 300px;
   padding: 20px;
   overflow-y: auto;
-`;
+`;

+ 84 - 66
dashboard/src/main/Login.tsx

@@ -1,39 +1,40 @@
-import React, { ChangeEvent, Component } from 'react';
-import styled from 'styled-components';
-import logo from 'assets/logo.png';
+import React, { ChangeEvent, Component } from "react";
+import styled from "styled-components";
+import logo from "assets/logo.png";
 
-import api from 'shared/api';
-import { emailRegex } from 'shared/regex';
-import { Context } from 'shared/Context';
+import api from "shared/api";
+import { emailRegex } from "shared/regex";
+import { Context } from "shared/Context";
 
 type PropsType = {
-  authenticate: () => void
+  authenticate: () => void;
 };
 
 type StateType = {
-  email: string,
-  password: string,
-  emailError: boolean,
-  credentialError: boolean
+  email: string;
+  password: string;
+  emailError: boolean;
+  credentialError: boolean;
 };
 
 export default class Login extends Component<PropsType, StateType> {
   state = {
-    email: '',
-    password: '',
+    email: "",
+    password: "",
     emailError: false,
-    credentialError: false
-  }
+    credentialError: false,
+  };
 
   handleKeyDown = (e: any) => {
-    e.key === 'Enter' ? this.handleLogin() : null;
-  }
+    e.key === "Enter" ? this.handleLogin() : null;
+  };
 
   componentDidMount() {
     let urlParams = new URLSearchParams(window.location.search);
-    let emailFromCLI = urlParams.get('email');
-    emailFromCLI ? this.setState({email: emailFromCLI}) :
-    document.addEventListener("keydown", this.handleKeyDown);
+    let emailFromCLI = urlParams.get("email");
+    emailFromCLI
+      ? this.setState({ email: emailFromCLI })
+      : document.addEventListener("keydown", this.handleKeyDown);
   }
 
   componentWillUnmount() {
@@ -50,42 +51,53 @@ 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) => {
-        // 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();
+      api.logInUser(
+        "",
+        {
+          email: email,
+          password: password,
+        },
+        {},
+        (err: any, res: any) => {
+          // 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();
+          }
         }
-      });
+      );
     }
-  }
+  };
 
   renderEmailError = () => {
     let { emailError } = this.state;
     if (emailError) {
       return (
-        <ErrorHelper><div />Please enter a valid email</ErrorHelper>
+        <ErrorHelper>
+          <div />
+          Please enter a valid email
+        </ErrorHelper>
       );
     }
-  }
+  };
 
   renderCredentialError = () => {
     let { credentialError } = this.state;
     if (credentialError) {
       return (
-        <ErrorHelper><div />Incorrect email or password</ErrorHelper>
+        <ErrorHelper>
+          <div />
+          Incorrect email or password
+        </ErrorHelper>
       );
     }
-  }
-  
+  };
+
   render() {
     let { email, password, credentialError, emailError } = this.state;
 
@@ -100,15 +112,15 @@ export default class Login extends Component<PropsType, StateType> {
             <Line />
             <Prompt>Log in to Porter</Prompt>
             <InputWrapper>
-              <Input 
-                type='email' 
-                placeholder='Email'
+              <Input
+                type="email"
+                placeholder="Email"
                 value={email}
-                onChange={(e: ChangeEvent<HTMLInputElement>) => 
-                  this.setState({ 
+                onChange={(e: ChangeEvent<HTMLInputElement>) =>
+                  this.setState({
                     email: e.target.value,
                     emailError: false,
-                    credentialError: false
+                    credentialError: false,
                   })
                 }
                 valid={!credentialError && !emailError}
@@ -116,14 +128,14 @@ export default class Login extends Component<PropsType, StateType> {
               {this.renderEmailError()}
             </InputWrapper>
             <InputWrapper>
-              <Input 
-                type='password' 
-                placeholder='Password'
+              <Input
+                type="password"
+                placeholder="Password"
                 value={password}
-                onChange={(e: ChangeEvent<HTMLInputElement>) => 
-                  this.setState({ 
-                    password: e.target.value, 
-                    credentialError: false 
+                onChange={(e: ChangeEvent<HTMLInputElement>) =>
+                  this.setState({
+                    password: e.target.value,
+                    credentialError: false,
                   })
                 }
                 valid={!credentialError}
@@ -132,8 +144,9 @@ export default class Login extends Component<PropsType, StateType> {
             </InputWrapper>
             <Button onClick={this.handleLogin}>Continue</Button>
 
-            <Helper>Don't have an account?
-              <Link href='/register'>Sign up</Link>
+            <Helper>
+              Don't have an account?
+              <Link href="/register">Sign up</Link>
             </Helper>
           </FormWrapper>
         </LoginPanel>
@@ -146,13 +159,13 @@ Login.contextType = Context;
 
 const Link = styled.a`
   margin-left: 5px;
-  color: #819BFD;
+  color: #819bfd;
 `;
 
 const Helper = styled.div`
   margin: 52px 0px 20px;
   font-size: 14px;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   color: #ffffff44;
 `;
 
@@ -174,7 +187,7 @@ const ErrorHelper = styled.div`
   width: 170px;
   user-select: none;
   background: #272731;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   font-size: 12px;
   display: flex;
   align-items: center;
@@ -207,12 +220,12 @@ const Button = styled.button`
   display: flex;
   justify-content: center;
   align-items: center;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   cursor: pointer;
   margin-top: 25px;
   border-radius: 2px;
   border: 0;
-  background: #819BFD;
+  background: #819bfd;
   color: white;
   font-weight: 500;
   font-size: 14px;
@@ -224,19 +237,20 @@ const InputWrapper = styled.div`
 
 const Input = styled.input`
   width: 200px;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   margin: 8px 0px;
   height: 30px;
   padding: 8px;
   background: #ffffff12;
   color: #ffffff;
-  border: ${(props: { valid?: boolean }) => props.valid ? '0' : '1px solid #ff3b62'};
+  border: ${(props: { valid?: boolean }) =>
+    props.valid ? "0" : "1px solid #ff3b62"};
   border-radius: 2px;
-  font-size: 14px
+  font-size: 14px;
 `;
 
 const Prompt = styled.div`
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   font-weight: 500;
   font-size: 15px;
   margin-bottom: 18px;
@@ -268,8 +282,12 @@ const GradientBg = styled.div`
   left: -40%;
   animation: flip 6s infinite linear;
   @keyframes flip {
-    from { transform: rotate(0deg); }
-    to { transform: rotate(360deg); }
+    from {
+      transform: rotate(0deg);
+    }
+    to {
+      transform: rotate(360deg);
+    }
   }
 `;
 

+ 0 - 1
dashboard/src/main/Main.tsx

@@ -1,7 +1,6 @@
 import React, { Component } from "react";
 import styled, { createGlobalStyle } from "styled-components";
 import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
-import close from "assets/close.png";
 
 import api from "shared/api";
 import { Context } from "shared/Context";

+ 86 - 70
dashboard/src/main/Register.tsx

@@ -1,35 +1,35 @@
-import React, { ChangeEvent, Component, useContext } from 'react';
-import styled from 'styled-components';
-import logo from 'assets/logo.png';
+import React, { ChangeEvent, Component, useContext } from "react";
+import styled from "styled-components";
+import logo from "assets/logo.png";
 
-import api from 'shared/api';
-import { emailRegex } from 'shared/regex';
-import { Context } from 'shared/Context';
+import api from "shared/api";
+import { emailRegex } from "shared/regex";
+import { Context } from "shared/Context";
 
 type PropsType = {
-  authenticate: () => void
+  authenticate: () => void;
 };
 
 type StateType = {
-  email: string,
-  password: string,
-  confirmPassword: string,
-  emailError: boolean,
-  confirmPasswordError: boolean
+  email: string;
+  password: string;
+  confirmPassword: string;
+  emailError: boolean;
+  confirmPasswordError: boolean;
 };
 
 export default class Register extends Component<PropsType, StateType> {
   state = {
-    email: '',
-    password: '',
-    confirmPassword: '',
+    email: "",
+    password: "",
+    confirmPassword: "",
     emailError: false,
-    confirmPasswordError: false
-  }
+    confirmPasswordError: false,
+  };
 
   handleKeyDown = (e: any) => {
-    e.key === 'Enter' ? this.handleRegister() : null;
-  }
+    e.key === "Enter" ? this.handleRegister() : null;
+  };
 
   componentDidMount() {
     document.addEventListener("keydown", this.handleKeyDown);
@@ -51,50 +51,60 @@ export default class Register extends Component<PropsType, StateType> {
     if (confirmPassword !== password) {
       this.setState({ confirmPasswordError: true });
     }
-    
+
     // Check for valid input
     if (emailRegex.test(email) && confirmPassword === password) {
-
       // Attempt user registration
-      api.registerUser('', {
-        email: email,
-        password: password
-      }, {}, (err: any, 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();
+      api.registerUser(
+        "",
+        {
+          email: email,
+          password: password,
+        },
+        {},
+        (err: any, 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();
+          }
         }
-      });
-    } 
+      );
+    }
   };
 
   renderEmailError = () => {
     let { emailError } = this.state;
     if (emailError) {
       return (
-        <ErrorHelper><div />Please enter a valid email</ErrorHelper>
+        <ErrorHelper>
+          <div />
+          Please enter a valid email
+        </ErrorHelper>
       );
     }
-  }
+  };
 
   renderConfirmPasswordError = () => {
     let { confirmPasswordError } = this.state;
     if (confirmPasswordError) {
       return (
-        <ErrorHelper><div />Passwords do not match</ErrorHelper>
+        <ErrorHelper>
+          <div />
+          Passwords do not match
+        </ErrorHelper>
       );
     }
-  }
+  };
 
   render() {
-    let { 
+    let {
       email,
-      password, 
-      confirmPassword, 
+      password,
+      confirmPassword,
       emailError,
-      confirmPasswordError
+      confirmPasswordError,
     } = this.state;
 
     return (
@@ -108,38 +118,38 @@ export default class Register extends Component<PropsType, StateType> {
             <Line />
             <Prompt>Create Account</Prompt>
             <InputWrapper>
-              <Input 
-                type='email'
-                placeholder='Email'
+              <Input
+                type="email"
+                placeholder="Email"
                 value={email}
-                onChange={(e: ChangeEvent<HTMLInputElement>) => 
+                onChange={(e: ChangeEvent<HTMLInputElement>) =>
                   this.setState({ email: e.target.value, emailError: false })
                 }
                 valid={!emailError}
               />
               {this.renderEmailError()}
             </InputWrapper>
-            <Input 
-              type='password' 
-              placeholder='Password'
+            <Input
+              type="password"
+              placeholder="Password"
               value={password}
-              onChange={(e: ChangeEvent<HTMLInputElement>) => 
-                this.setState({ 
+              onChange={(e: ChangeEvent<HTMLInputElement>) =>
+                this.setState({
                   password: e.target.value,
-                  confirmPasswordError: false
+                  confirmPasswordError: false,
                 })
               }
               valid={true}
             />
             <InputWrapper>
-              <Input 
-                type='password' 
-                placeholder='Confirm Password'
+              <Input
+                type="password"
+                placeholder="Confirm Password"
                 value={confirmPassword}
-                onChange={(e: ChangeEvent<HTMLInputElement>) => 
-                  this.setState({ 
-                    confirmPassword: e.target.value, 
-                    confirmPasswordError: false 
+                onChange={(e: ChangeEvent<HTMLInputElement>) =>
+                  this.setState({
+                    confirmPassword: e.target.value,
+                    confirmPasswordError: false,
                   })
                 }
                 valid={!confirmPasswordError}
@@ -148,8 +158,9 @@ export default class Register extends Component<PropsType, StateType> {
             </InputWrapper>
             <Button onClick={this.handleRegister}>Continue</Button>
 
-            <Helper>Have an account?
-              <Link href='/login'>Sign in</Link>
+            <Helper>
+              Have an account?
+              <Link href="/login">Sign in</Link>
             </Helper>
           </FormWrapper>
         </LoginPanel>
@@ -162,13 +173,13 @@ Register.contextType = Context;
 
 const Link = styled.a`
   margin-left: 5px;
-  color: #819BFD;
+  color: #819bfd;
 `;
 
 const Helper = styled.div`
   margin: 30px 0px 20px;
   font-size: 14px;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   color: #ffffff44;
 `;
 
@@ -194,7 +205,7 @@ const ErrorHelper = styled.div`
   width: 170px;
   user-select: none;
   background: #272731;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   font-size: 12px;
   display: flex;
   align-items: center;
@@ -227,12 +238,12 @@ const Button = styled.button`
   display: flex;
   justify-content: center;
   align-items: center;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   cursor: pointer;
   margin-top: 25px;
   border-radius: 2px;
   border: 0;
-  background: #819BFD;
+  background: #819bfd;
   color: white;
   font-weight: 500;
   font-size: 14px;
@@ -240,19 +251,20 @@ const Button = styled.button`
 
 const Input = styled.input`
   width: 200px;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   margin: 8px 0px;
   height: 30px;
   padding: 8px;
   background: #ffffff12;
   color: #ffffff;
-  border: ${(props: { valid?: boolean }) => props.valid ? '0' : '1px solid #ff3b62'};
+  border: ${(props: { valid?: boolean }) =>
+    props.valid ? "0" : "1px solid #ff3b62"};
   border-radius: 2px;
-  font-size: 14px
+  font-size: 14px;
 `;
 
 const Prompt = styled.div`
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   font-weight: 500;
   font-size: 15px;
   margin-bottom: 18px;
@@ -284,8 +296,12 @@ const GradientBg = styled.div`
   left: -40%;
   animation: flip 6s infinite linear;
   @keyframes flip {
-    from { transform: rotate(0deg); }
-    to { transform: rotate(360deg); }
+    from {
+      transform: rotate(0deg);
+    }
+    to {
+      transform: rotate(360deg);
+    }
   }
 `;
 

+ 87 - 47
dashboard/src/main/home/navbar/Feedback.tsx

@@ -1,30 +1,30 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
+import React, { Component } from "react";
+import styled from "styled-components";
 
-import { Context } from 'shared/Context';
-import { handleSubmitFeedback } from 'shared/feedback';
+import { Context } from "shared/Context";
+import { handleSubmitFeedback } from "shared/feedback";
 
 type PropsType = {
-  currentView: string,
+  currentView: string;
 };
 
 type StateType = {
-  feedbackSent: boolean,
-  showFeedbackDropdown: boolean,
-  feedbackText: string,
+  feedbackSent: boolean;
+  showFeedbackDropdown: boolean;
+  feedbackText: string;
 };
 
 export default class Feedback extends Component<PropsType, StateType> {
   state = {
     feedbackSent: false,
     showFeedbackDropdown: false,
-    feedbackText: '',
-  }
+    feedbackText: "",
+  };
 
   renderReceipt = () => {
     if (this.state.feedbackSent) {
       return (
-        <DropdownAlt dropdownWidth='300px' dropdownMaxHeight='200px'>
+        <DropdownAlt dropdownWidth="300px" dropdownMaxHeight="200px">
           <ConfirmationMessage>
             <i className="material-icons-outlined">emoji_food_beverage</i>
             Thanks for improving Porter.
@@ -32,35 +32,48 @@ export default class Feedback extends Component<PropsType, StateType> {
         </DropdownAlt>
       );
     }
-  }
+  };
 
   onSubmitFeedback = () => {
     let { user } = this.context;
-    let msg = '👤 ' + user.email + ' 📍 ' + this.props.currentView + ': ' + this.state.feedbackText;
+    let msg =
+      "👤 " +
+      user.email +
+      " 📍 " +
+      this.props.currentView +
+      ": " +
+      this.state.feedbackText;
     handleSubmitFeedback(msg, () => {
-      this.setState({ feedbackSent: true, feedbackText: '' });
+      this.setState({ feedbackSent: true, feedbackText: "" });
     });
-  }
+  };
 
   renderFeedbackDropdown = () => {
     if (this.state.showFeedbackDropdown) {
-      let disabled = this.state.feedbackText === '';
+      let disabled = this.state.feedbackText === "";
       return (
         <>
-          <CloseOverlay onClick={() => this.setState({ showFeedbackDropdown: false, feedbackSent: false })} />
-          <Dropdown 
-            feedbackSent={this.state.feedbackSent} 
-            dropdownWidth='300px' 
-            dropdownMaxHeight='200px'
+          <CloseOverlay
+            onClick={() =>
+              this.setState({
+                showFeedbackDropdown: false,
+                feedbackSent: false,
+              })
+            }
+          />
+          <Dropdown
+            feedbackSent={this.state.feedbackSent}
+            dropdownWidth="300px"
+            dropdownMaxHeight="200px"
           >
-            <FeedbackInput 
+            <FeedbackInput
               autoFocus={true}
               value={this.state.feedbackText}
               onChange={(e) => this.setState({ feedbackText: e.target.value })}
-              placeholder='Help us improve this page.' 
+              placeholder="Help us improve this page."
             />
-            <SendButton 
-              disabled={disabled} 
+            <SendButton
+              disabled={disabled}
               onClick={() => !disabled && this.onSubmitFeedback()}
             >
               <i className="material-icons">send</i> Send
@@ -70,15 +83,19 @@ export default class Feedback extends Component<PropsType, StateType> {
         </>
       );
     }
-  }
+  };
 
   render() {
     return (
       <FeedbackButton>
-        <Flex onClick={() => this.setState({ showFeedbackDropdown: !this.state.showFeedbackDropdown })}>
-          <i className="material-icons-outlined">
-            campaign
-          </i>
+        <Flex
+          onClick={() =>
+            this.setState({
+              showFeedbackDropdown: !this.state.showFeedbackDropdown,
+            })
+          }
+        >
+          <i className="material-icons-outlined">campaign</i>
           Feedback?
         </Flex>
         {this.renderFeedbackDropdown()}
@@ -122,14 +139,16 @@ const SendButton = styled.div`
   display: flex;
   align-items: center;
   height: 40px;
-  cursor: ${(props: { disabled: boolean }) => props.disabled ? 'not-allowed' : 'pointer'};
+  cursor: ${(props: { disabled: boolean }) =>
+    props.disabled ? "not-allowed" : "pointer"};
   justify-content: center;
   margin-top: -3px;
   font-size: 13px;
   font-weight: 500;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   :hover {
-    background: ${(props: { disabled: boolean }) => props.disabled ? '' : '#ffffff11'};
+    background: ${(props: { disabled: boolean }) =>
+      props.disabled ? "" : "#ffffff11"};
   }
 
   > i {
@@ -155,7 +174,7 @@ const FeedbackInput = styled.textarea`
   color: white;
   border: 0;
   font-size: 13px;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   background: #aaaabb11;
 `;
 
@@ -170,21 +189,35 @@ const Dropdown = styled.div`
   right: 0;
   top: calc(100% + 5px);
   background: #26282f;
-  width: ${(props: { dropdownWidth: string, dropdownMaxHeight: string, feedbackSent?: boolean }) => props.dropdownWidth};
-  max-height: ${(props: { dropdownWidth: string, dropdownMaxHeight: string, feedbackSent?: boolean }) => props.dropdownMaxHeight ? props.dropdownMaxHeight : '300px'};
+  width: ${(props: {
+    dropdownWidth: string;
+    dropdownMaxHeight: string;
+    feedbackSent?: boolean;
+  }) => props.dropdownWidth};
+  max-height: ${(props: {
+    dropdownWidth: string;
+    dropdownMaxHeight: string;
+    feedbackSent?: boolean;
+  }) => (props.dropdownMaxHeight ? props.dropdownMaxHeight : "300px")};
   border-radius: 3px;
   z-index: 999;
   overflow-y: auto;
   margin-bottom: 20px;
   box-shadow: 0 8px 20px 0px #00000088;
-  animation: ${(props: { dropdownWidth: string, dropdownMaxHeight: string, feedbackSent?: boolean }) => props.feedbackSent ? 'flyOff 0.3s 0.05s' : ''};
+  animation: ${(props: {
+    dropdownWidth: string;
+    dropdownMaxHeight: string;
+    feedbackSent?: boolean;
+  }) => (props.feedbackSent ? "flyOff 0.3s 0.05s" : "")};
   animation-fill-mode: forwards;
   @keyframes flyOff {
     from {
-      opacity: 1; transform: translateX(0px);
+      opacity: 1;
+      transform: translateX(0px);
     }
     to {
-      opacity: 0; transform: translateX(100px);
+      opacity: 0;
+      transform: translateX(100px);
     }
   }
 `;
@@ -194,8 +227,12 @@ const DropdownAlt = styled(Dropdown)`
   opacity: 0;
   animation-fill-mode: forwards;
   @keyframes fadeIn {
-    from { opacity: 0 }
-    to { opacity: 1 }
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
   }
 `;
 
@@ -210,17 +247,19 @@ const NavButton = styled.a`
       color: #ffffff;
     }
   }
-  
+
   > i {
     cursor: pointer;
-    color: ${(props: { selected?: boolean }) => props.selected ? '#ffffff' : '#ffffff88'};
+    color: ${(props: { selected?: boolean }) =>
+      props.selected ? "#ffffff" : "#ffffff88"};
     font-size: 24px;
   }
 `;
 
 const FeedbackButton = styled(NavButton)`
-  color: ${(props: { selected?: boolean }) => props.selected ? '#ffffff' : '#ffffff88'};
-  font-family: 'Work Sans', sans-serif;
+  color: ${(props: { selected?: boolean }) =>
+    props.selected ? "#ffffff" : "#ffffff88"};
+  font-family: "Work Sans", sans-serif;
   font-size: 14px;
   margin-right: 20px;
   :hover {
@@ -234,9 +273,10 @@ const FeedbackButton = styled(NavButton)`
 
   > div {
     > i {
-      color: ${(props: { selected?: boolean }) => props.selected ? '#ffffff' : '#ffffff88'};
+      color: ${(props: { selected?: boolean }) =>
+        props.selected ? "#ffffff" : "#ffffff88"};
       font-size: 26px;
       margin-right: 6px;
     }
   }
-`;
+`;

+ 64 - 35
dashboard/src/main/home/navbar/Navbar.tsx

@@ -1,42 +1,46 @@
-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 { Context } from 'shared/Context';
+import api from "shared/api";
+import { Context } from "shared/Context";
 
-import Feedback from './Feedback';
+import Feedback from "./Feedback";
 
 type PropsType = {
-  logOut: () => void,
-  currentView: string,
+  logOut: () => void;
+  currentView: string;
 };
 
 type StateType = {
-  showDropdown: boolean,
+  showDropdown: boolean;
 };
 
 export default class Navbar extends Component<PropsType, StateType> {
   state = {
     showDropdown: false,
-  }
+  };
 
   handleLogout = (): void => {
     let { logOut } = this.props;
     let { setCurrentError } = this.context;
 
     // Attempt user logout
-    api.logOutUser('<token>', {}, {}, (err: any, res: any) => {
+    api.logOutUser("<token>", {}, {}, (err: any, res: any) => {
       err ? setCurrentError(err.response.data.errors[0]) : logOut();
-    }); 
-  }
+    });
+  };
 
   renderSettingsDropdown = () => {
     if (this.state.showDropdown) {
       return (
         <>
-          <CloseOverlay onClick={() => this.setState({ showDropdown: false })} />
-          <Dropdown dropdownWidth='250px' dropdownMaxHeight='200px'>
-            <DropdownLabel>{this.context.user && this.context.user.email}</DropdownLabel>
+          <CloseOverlay
+            onClick={() => this.setState({ showDropdown: false })}
+          />
+          <Dropdown dropdownWidth="250px" dropdownMaxHeight="200px">
+            <DropdownLabel>
+              {this.context.user && this.context.user.email}
+            </DropdownLabel>
             <LogOutButton onClick={this.handleLogout}>
               <i className="material-icons">keyboard_return</i> Log Out
             </LogOutButton>
@@ -44,16 +48,18 @@ export default class Navbar extends Component<PropsType, StateType> {
         </>
       );
     }
-  }
+  };
 
   render() {
     return (
       <StyledNavbar>
         <Feedback currentView={this.props.currentView} />
         <NavButton selected={this.state.showDropdown}>
-          <i 
-            className="material-icons-outlined" 
-            onClick={() => this.setState({ showDropdown: !this.state.showDropdown })}
+          <i
+            className="material-icons-outlined"
+            onClick={() =>
+              this.setState({ showDropdown: !this.state.showDropdown })
+            }
           >
             account_circle
           </i>
@@ -81,15 +87,17 @@ const LogOutButton = styled.button`
   height: 40px;
   font-size: 13px;
   font-weight: 500;
-  font-family: 'Work Sans', sans-serif;
+  font-family: "Work Sans", sans-serif;
   color: white;
   width: 100%;
   border: 0;
   text-align: left;
   background: none;
-  cursor: ${(props) => (!props.disabled ? 'pointer' : 'default')};
+  cursor: ${(props) => (!props.disabled ? "pointer" : "default")};
   user-select: none;
-  :focus { outline: 0 }
+  :focus {
+    outline: 0;
+  }
   :hover {
     background: #ffffff11;
   }
@@ -130,21 +138,35 @@ const Dropdown = styled.div`
   right: 0;
   top: calc(100% + 5px);
   background: #26282f;
-  width: ${(props: { dropdownWidth: string, dropdownMaxHeight: string, feedbackSent?: boolean }) => props.dropdownWidth};
-  max-height: ${(props: { dropdownWidth: string, dropdownMaxHeight: string, feedbackSent?: boolean }) => props.dropdownMaxHeight ? props.dropdownMaxHeight : '300px'};
+  width: ${(props: {
+    dropdownWidth: string;
+    dropdownMaxHeight: string;
+    feedbackSent?: boolean;
+  }) => props.dropdownWidth};
+  max-height: ${(props: {
+    dropdownWidth: string;
+    dropdownMaxHeight: string;
+    feedbackSent?: boolean;
+  }) => (props.dropdownMaxHeight ? props.dropdownMaxHeight : "300px")};
   border-radius: 3px;
   z-index: 999;
   overflow-y: auto;
   margin-bottom: 20px;
   box-shadow: 0 8px 20px 0px #00000088;
-  animation: ${(props: { dropdownWidth: string, dropdownMaxHeight: string, feedbackSent?: boolean }) => props.feedbackSent ? 'flyOff 0.3s 0.05s' : ''};
+  animation: ${(props: {
+    dropdownWidth: string;
+    dropdownMaxHeight: string;
+    feedbackSent?: boolean;
+  }) => (props.feedbackSent ? "flyOff 0.3s 0.05s" : "")};
   animation-fill-mode: forwards;
   @keyframes flyOff {
     from {
-      opacity: 1; transform: translateX(0px);
+      opacity: 1;
+      transform: translateX(0px);
     }
     to {
-      opacity: 0; transform: translateX(100px);
+      opacity: 0;
+      transform: translateX(100px);
     }
   }
 `;
@@ -154,8 +176,12 @@ const DropdownAlt = styled(Dropdown)`
   opacity: 0;
   animation-fill-mode: forwards;
   @keyframes fadeIn {
-    from { opacity: 0 }
-    to { opacity: 1 }
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
   }
 `;
 
@@ -182,17 +208,19 @@ const NavButton = styled.a`
       color: #ffffff;
     }
   }
-  
+
   > i {
     cursor: pointer;
-    color: ${(props: { selected?: boolean }) => props.selected ? '#ffffff' : '#ffffff88'};
+    color: ${(props: { selected?: boolean }) =>
+      props.selected ? "#ffffff" : "#ffffff88"};
     font-size: 24px;
   }
 `;
 
 const FeedbackButton = styled(NavButton)`
-  color: ${(props: { selected?: boolean }) => props.selected ? '#ffffff' : '#ffffff88'};
-  font-family: 'Work Sans', sans-serif;
+  color: ${(props: { selected?: boolean }) =>
+    props.selected ? "#ffffff" : "#ffffff88"};
+  font-family: "Work Sans", sans-serif;
   font-size: 14px;
   margin-right: 20px;
   :hover {
@@ -206,9 +234,10 @@ const FeedbackButton = styled(NavButton)`
 
   > div {
     > i {
-      color: ${(props: { selected?: boolean }) => props.selected ? '#ffffff' : '#ffffff88'};
+      color: ${(props: { selected?: boolean }) =>
+        props.selected ? "#ffffff" : "#ffffff88"};
       font-size: 26px;
       margin-right: 6px;
     }
   }
-`;
+`;

+ 11 - 16
dashboard/src/shared/Context.tsx

@@ -1,12 +1,6 @@
-import React, { Component } from 'react';
+import React, { Component } from "react";
 
-import { ProjectType, ClusterType } from 'shared/types';
-
-type PropsType = {
-}
-
-type StateType = {
-}
+import { ProjectType, ClusterType } from "shared/types";
 
 const Context = React.createContext({});
 
@@ -37,21 +31,24 @@ class ContextProvider extends Component {
     },
     currentCluster: null as ClusterType | null,
     setCurrentCluster: (currentCluster: ClusterType, callback?: any) => {
-      localStorage.setItem(this.state.currentProject.id + '-cluster', JSON.stringify(currentCluster));
+      localStorage.setItem(
+        this.state.currentProject.id + "-cluster",
+        JSON.stringify(currentCluster)
+      );
       this.setState({ currentCluster }, () => {
         callback && callback();
       });
     },
     currentProject: null as ProjectType | null,
     setCurrentProject: (currentProject: ProjectType, callback?: any) => {
-      localStorage.setItem('currentProject', currentProject.id.toString());
+      localStorage.setItem("currentProject", currentProject.id.toString());
       this.setState({ currentProject }, () => {
         callback && callback();
       });
     },
     projects: [] as ProjectType[],
     setProjects: (projects: ProjectType[]) => {
-      projects.sort((a: any, b: any) => (a.name > b.name) ? 1 : -1);
+      projects.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
       this.setState({ projects });
     },
     user: null as any,
@@ -73,13 +70,11 @@ class ContextProvider extends Component {
         user: null,
         devOpsMode: true,
       });
-    }
+    },
   };
-  
+
   render() {
-    return (
-      <Provider value={this.state}>{this.props.children}</Provider>
-    );
+    return <Provider value={this.state}>{this.props.children}</Provider>;
   }
 }
 

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

@@ -1,134 +1,134 @@
 /* eslint-disable no-plusplus, no-continue */
 const foregroundColors = {
-    '30': 'black',
-    '31': 'red',
-    '32': 'green',
-    '33': 'yellow',
-    '34': 'blue',
-    '35': 'magenta',
-    '36': 'cyan',
-    '37': 'white',
-    '90': 'grey',
-  } as Record<string, string>;
-
-  const backgroundColors = {
-    '40': 'black',
-    '41': 'red',
-    '42': 'green',
-    '43': 'yellow',
-    '44': 'blue',
-    '45': 'magenta',
-    '46': 'cyan',
-    '47': 'white',
-  } as Record<string, string>;
-
-  const styles = {
-    '1': 'bold',
-    '3': 'italic',
-    '4': 'underline',
-  } as Record<string, string>;
-
-  const eraseChar = (matchingText: any, result: any) => {
-    if (matchingText.length) {
-      return [matchingText.substr(0, matchingText.length - 1), result];
-    } else if (result.length) {
-      const index = result.length - 1;
-      const { text } = result[index];
-      const newResult =
-        text.length === 1
-          ? result.slice(0, result.length - 1)
-          : result.map((item: any, i: number) =>
-              index === i
-                ? { ...item, text: text.substr(0, text.length - 1) }
-                : item
-            );
-  
-      return [matchingText, newResult];
-    }
-  
-    return [matchingText, result];
-  };
-  
-  const ansiparse = (str: string) => {
-    let matchingControl = null;
-    let matchingData = null;
-    let matchingText = '';
-    let ansiState = [] as any[];
-    let result = [] as any[];
-    let state = {} as any;
-  
-    for (let i = 0; i < str.length; i++) {
-      if (matchingControl !== null) {
-        if (matchingControl === '\x1b' && str[i] === '[') {
-          if (matchingText) {
-            state.text = matchingText;
-            result.push(state);
-            state = {};
-            matchingText = '';
-          }
-  
-          matchingControl = null;
-          matchingData = '';
-        } else {
-          matchingText += matchingControl + str[i];
-          matchingControl = null;
+  "30": "black",
+  "31": "red",
+  "32": "green",
+  "33": "yellow",
+  "34": "blue",
+  "35": "magenta",
+  "36": "cyan",
+  "37": "white",
+  "90": "grey",
+} as Record<string, string>;
+
+const backgroundColors = {
+  "40": "black",
+  "41": "red",
+  "42": "green",
+  "43": "yellow",
+  "44": "blue",
+  "45": "magenta",
+  "46": "cyan",
+  "47": "white",
+} as Record<string, string>;
+
+const styles = {
+  "1": "bold",
+  "3": "italic",
+  "4": "underline",
+} as Record<string, string>;
+
+const eraseChar = (matchingText: any, result: any) => {
+  if (matchingText.length) {
+    return [matchingText.substr(0, matchingText.length - 1), result];
+  } else if (result.length) {
+    const index = result.length - 1;
+    const { text } = result[index];
+    const newResult =
+      text.length === 1
+        ? result.slice(0, result.length - 1)
+        : result.map((item: any, i: number) =>
+            index === i
+              ? { ...item, text: text.substr(0, text.length - 1) }
+              : item
+          );
+
+    return [matchingText, newResult];
+  }
+
+  return [matchingText, result];
+};
+
+const ansiparse = (str: string) => {
+  let matchingControl = null;
+  let matchingData = null;
+  let matchingText = "";
+  let ansiState = [] as any[];
+  let result = [] as any[];
+  let state = {} as any;
+
+  for (let i = 0; i < str.length; i++) {
+    if (matchingControl !== null) {
+      if (matchingControl === "\x1b" && str[i] === "[") {
+        if (matchingText) {
+          state.text = matchingText;
+          result.push(state);
+          state = {};
+          matchingText = "";
         }
-  
-        continue;
-      } else if (matchingData !== null) {
-        if (str[i] === ';') {
-          ansiState.push(matchingData);
-          matchingData = '';
-        } else if (str[i] === 'm') {
-          ansiState.push(matchingData);
-          matchingData = null;
-          matchingText = '';
-  
-          for (let a = 0; a < ansiState.length; a++) {
-            const ansiCode = ansiState[a];
-  
-            if (foregroundColors[ansiCode]) {
-              state.foreground = foregroundColors[ansiCode];
-            } else if (backgroundColors[ansiCode]) {
-              state.background = backgroundColors[ansiCode];
-            } else if (ansiCode === 39) {
-              delete state.foreground;
-            } else if (ansiCode === 49) {
-              delete state.background;
-            } else if (styles[ansiCode]) {
-              state[styles[ansiCode]] = true;
-            } else if (ansiCode === 22) {
-              state.bold = false;
-            } else if (ansiCode === 23) {
-              state.italic = false;
-            } else if (ansiCode === 24) {
-              state.underline = false;
-            }
+
+        matchingControl = null;
+        matchingData = "";
+      } else {
+        matchingText += matchingControl + str[i];
+        matchingControl = null;
+      }
+
+      continue;
+    } else if (matchingData !== null) {
+      if (str[i] === ";") {
+        ansiState.push(matchingData);
+        matchingData = "";
+      } else if (str[i] === "m") {
+        ansiState.push(matchingData);
+        matchingData = null;
+        matchingText = "";
+
+        for (let a = 0; a < ansiState.length; a++) {
+          const ansiCode = ansiState[a];
+
+          if (foregroundColors[ansiCode]) {
+            state.foreground = foregroundColors[ansiCode];
+          } else if (backgroundColors[ansiCode]) {
+            state.background = backgroundColors[ansiCode];
+          } else if (ansiCode === 39) {
+            delete state.foreground;
+          } else if (ansiCode === 49) {
+            delete state.background;
+          } else if (styles[ansiCode]) {
+            state[styles[ansiCode]] = true;
+          } else if (ansiCode === 22) {
+            state.bold = false;
+          } else if (ansiCode === 23) {
+            state.italic = false;
+          } else if (ansiCode === 24) {
+            state.underline = false;
           }
-  
-          ansiState = [];
-        } else {
-          matchingData += str[i];
         }
-  
-        continue;
-      }
-  
-      if (str[i] === '\x1b') {
-        matchingControl = str[i];
-      } else if (str[i] === '\u0008') {
-        [matchingText, result] = eraseChar(matchingText, result);
+
+        ansiState = [];
       } else {
-        matchingText += str[i];
+        matchingData += str[i];
       }
+
+      continue;
     }
-  
-    if (matchingText) {
-      state.text = matchingText + (matchingControl || '');
-      result.push(state);
+
+    if (str[i] === "\x1b") {
+      matchingControl = str[i];
+    } else if (str[i] === "\u0008") {
+      [matchingText, result] = eraseChar(matchingText, result);
+    } else {
+      matchingText += str[i];
     }
-  
-    return result;
-  };
-  
-  export default ansiparse;
+  }
+
+  if (matchingText) {
+    state.text = matchingText + (matchingControl || "");
+    result.push(state);
+  }
+
+  return result;
+};
+
+export default ansiparse;

+ 388 - 273
dashboard/src/shared/api.tsx

@@ -1,7 +1,6 @@
-import axios from 'axios';
-import { baseApi } from './baseApi';
+import { baseApi } from "./baseApi";
 
-import { StorageType } from './types';
+import { StorageType } from "./types";
 
 /**
  * Generic api call format
@@ -11,426 +10,542 @@ import { StorageType } from './types';
  * @param {(err: Object, res: Object) => void} callback - Callback function.
  */
 
-const checkAuth = baseApi('GET', '/api/auth/check');
-
-const createAWSIntegration = baseApi<{
-  aws_region: string,
-  aws_cluster_id?: string,
-  aws_access_key_id: string,
-  aws_secret_access_key: string,
-}, { id: number }>('POST', pathParams => {
+const checkAuth = baseApi("GET", "/api/auth/check");
+
+const createAWSIntegration = baseApi<
+  {
+    aws_region: string;
+    aws_cluster_id?: string;
+    aws_access_key_id: string;
+    aws_secret_access_key: string;
+  },
+  { id: number }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.id}/integrations/aws`;
 });
 
-const createDOCR = baseApi<{
-  do_integration_id: number,
-  docr_name: string,
-  docr_subscription_tier: string,
-}, {
-  project_id: number,
-}>('POST', pathParams => {
+const createDOCR = baseApi<
+  {
+    do_integration_id: number;
+    docr_name: string;
+    docr_subscription_tier: string;
+  },
+  {
+    project_id: number;
+  }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/provision/docr`;
 });
 
-const createDOKS = baseApi<{
-  do_integration_id: number,
-  doks_name: string,
-  do_region: string,
-}, {
-  project_id: number,
-}>('POST', pathParams => {
+const createDOKS = baseApi<
+  {
+    do_integration_id: number;
+    doks_name: string;
+    do_region: string;
+  },
+  {
+    project_id: number;
+  }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/provision/doks`;
 });
 
-const createECR = baseApi<{
-  name: string,
-  aws_integration_id: string,
-}, { id: number }>('POST', pathParams => {
+const createECR = baseApi<
+  {
+    name: string;
+    aws_integration_id: string;
+  },
+  { id: number }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.id}/registries`;
 });
 
-const createGCPIntegration = baseApi<{
-  gcp_region: string,
-  gcp_key_data: string,
-  gcp_project_id: string,
-}, {
-  project_id: number,
-}>('POST', pathParams => {
+const createGCPIntegration = baseApi<
+  {
+    gcp_region: string;
+    gcp_key_data: string;
+    gcp_project_id: string;
+  },
+  {
+    project_id: number;
+  }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/integrations/gcp`;
 });
 
-const createGCR = baseApi<{
-  gcp_integration_id: number,
-}, {
-  project_id: number,
-}>('POST', pathParams => {
+const createGCR = baseApi<
+  {
+    gcp_integration_id: number;
+  },
+  {
+    project_id: number;
+  }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/provision/gcr`;
 });
 
-const createGHAction = baseApi<{
-  git_repo: string,
-  image_repo_uri: string,
-  dockerfile_path: string,
-  git_repo_id: number,
-}, {
-  project_id: number,
-  CLUSTER_ID: number,
-  RELEASE_NAME: string,
-  RELEASE_NAMESPACE: string,
-}>('POST', pathParams => {
+const createGHAction = baseApi<
+  {
+    git_repo: string;
+    image_repo_uri: string;
+    dockerfile_path: string;
+    git_repo_id: number;
+  },
+  {
+    project_id: number;
+    CLUSTER_ID: number;
+    RELEASE_NAME: string;
+    RELEASE_NAMESPACE: string;
+  }
+>("POST", (pathParams) => {
   let { project_id, CLUSTER_ID, RELEASE_NAME, RELEASE_NAMESPACE } = pathParams;
   return `/api/projects/${project_id}/ci/actions?cluster_id=${CLUSTER_ID}&name=${RELEASE_NAME}&namespace=${RELEASE_NAMESPACE}`;
-})
-
-const createGKE = baseApi<{
-  gcp_integration_id: number,
-  gke_name: string,
-}, {
-  project_id: number,
-}>('POST', pathParams => {
+});
+
+const createGKE = baseApi<
+  {
+    gcp_integration_id: number;
+    gke_name: string;
+  },
+  {
+    project_id: number;
+  }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/provision/gke`;
 });
 
-const createInvite = baseApi<{
-  email: string
-}, {
-  id: number
-}>('POST', pathParams => {
+const createInvite = baseApi<
+  {
+    email: string;
+  },
+  {
+    id: number;
+  }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.id}/invites`;
 });
 
-const createProject = baseApi<{ name: string }, {}>('POST', pathParams => {
+const createProject = baseApi<{ name: string }, {}>("POST", (pathParams) => {
   return `/api/projects`;
 });
 
-const deleteCluster = baseApi<{
-}, {
-  project_id: number,
-  cluster_id: number,
-}>('DELETE', pathParams => {
+const deleteCluster = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+  }
+>("DELETE", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}`;
 });
 
-const deleteInvite = baseApi<{}, { id: number, invId: number }>('DELETE', pathParams => {
-  return `/api/projects/${pathParams.id}/invites/${pathParams.invId}`;
-});
+const deleteInvite = baseApi<{}, { id: number; invId: number }>(
+  "DELETE",
+  (pathParams) => {
+    return `/api/projects/${pathParams.id}/invites/${pathParams.invId}`;
+  }
+);
 
-const deleteProject = baseApi<{}, { id: number }>('DELETE', pathParams => {
+const deleteProject = baseApi<{}, { id: number }>("DELETE", (pathParams) => {
   return `/api/projects/${pathParams.id}`;
 });
 
-const deployTemplate = baseApi<{
-  templateName: string,
-  imageURL?: string,
-  formValues?: any,
-  storage: StorageType,
-  namespace: string,
-  name: string,
-}, { 
-  id: number,
-  cluster_id: number, 
-  name: string, 
-  version: string 
-}>('POST', pathParams => {
+const deployTemplate = baseApi<
+  {
+    templateName: string;
+    imageURL?: string;
+    formValues?: any;
+    storage: StorageType;
+    namespace: string;
+    name: string;
+  },
+  {
+    id: number;
+    cluster_id: number;
+    name: string;
+    version: string;
+  }
+>("POST", (pathParams) => {
   let { cluster_id, id, name, version } = pathParams;
   return `/api/projects/${id}/deploy/${name}/${version}?cluster_id=${cluster_id}`;
 });
 
-const destroyCluster = baseApi<{
-  eks_name: string,
-}, {
-  project_id: number,
-  infra_id: number,
-}>('POST', pathParams => {
+const destroyCluster = baseApi<
+  {
+    eks_name: string;
+  },
+  {
+    project_id: number;
+    infra_id: number;
+  }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/eks/destroy`;
 });
 
-const getBranchContents = baseApi<{ 
-  dir: string 
-}, {
-  project_id: number,
-  git_repo_id: number
-  kind: string,
-  owner: string,
-  name: string,
-  branch: string
-}>('GET', pathParams => {
+const getBranchContents = baseApi<
+  {
+    dir: string;
+  },
+  {
+    project_id: number;
+    git_repo_id: number;
+    kind: string;
+    owner: string;
+    name: string;
+    branch: string;
+  }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id}/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name}/${pathParams.branch}/contents`;
 });
 
-const getBranches = baseApi<{
-}, {
-  project_id: number,
-  git_repo_id: number,
-  kind: string,
-  owner: string,
-  name: string
-}>('GET', pathParams => {
+const getBranches = baseApi<
+  {},
+  {
+    project_id: number;
+    git_repo_id: number;
+    kind: string;
+    owner: string;
+    name: string;
+  }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id}/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name}/branches`;
 });
 
-const getChart = baseApi<{
-  namespace: string,
-  cluster_id: number,
-  storage: StorageType
-}, { id: number, name: string, revision: number }>('GET', pathParams => {
+const getChart = baseApi<
+  {
+    namespace: string;
+    cluster_id: number;
+    storage: StorageType;
+  },
+  { id: number; name: string; revision: number }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}`;
 });
 
-const getCharts = baseApi<{
-  namespace: string,
-  cluster_id: number,
-  storage: StorageType,
-  limit: number,
-  skip: number,
-  byDate: boolean,
-  statusFilter: string[]
-}, { id: number }>('GET', pathParams => {
+const getCharts = baseApi<
+  {
+    namespace: string;
+    cluster_id: number;
+    storage: StorageType;
+    limit: number;
+    skip: number;
+    byDate: boolean;
+    statusFilter: string[];
+  },
+  { id: number }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/releases`;
 });
 
-const getChartComponents = baseApi<{
-  namespace: string,
-  cluster_id: number,
-  storage: StorageType
-}, { id: number, name: string, revision: number }>('GET', pathParams => {
+const getChartComponents = baseApi<
+  {
+    namespace: string;
+    cluster_id: number;
+    storage: StorageType;
+  },
+  { id: number; name: string; revision: number }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}/components`;
 });
 
-const getChartControllers = baseApi<{
-  namespace: string,
-  cluster_id: number,
-  storage: StorageType
-}, { id: number, name: string, revision: number }>('GET', pathParams => {
+const getChartControllers = baseApi<
+  {
+    namespace: string;
+    cluster_id: number;
+    storage: StorageType;
+  },
+  { id: number; name: string; revision: number }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}/controllers`;
 });
 
-const getClusterIntegrations = baseApi('GET', '/api/integrations/cluster');
+const getClusterIntegrations = baseApi("GET", "/api/integrations/cluster");
 
-const getClusters = baseApi<{}, { id: number }>('GET', pathParams => {
+const getClusters = baseApi<{}, { id: number }>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/clusters`;
 });
 
-const getGitRepoList = baseApi<{
-}, {
-  project_id: number,
-  git_repo_id: number,
-}>('GET', pathParams => {
+const getGitRepoList = baseApi<
+  {},
+  {
+    project_id: number;
+    git_repo_id: number;
+  }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id}/repos`;
 });
 
-const getGitRepos = baseApi<{
-}, {
-  project_id: number,
-}>('GET', pathParams => {
+const getGitRepos = baseApi<
+  {},
+  {
+    project_id: number;
+  }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/gitrepos`;
 });
 
-const getImageRepos = baseApi<{
-}, {
-  project_id: number, 
-  registry_id: number 
-}>('GET', pathParams => {
+const getImageRepos = baseApi<
+  {},
+  {
+    project_id: number;
+    registry_id: number;
+  }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/registries/${pathParams.registry_id}/repositories`;
 });
 
-const getImageTags = baseApi<{
-}, {   
-  project_id: number,
-  registry_id: number,
-  repo_name: string,
-}>('GET', pathParams => {
+const getImageTags = baseApi<
+  {},
+  {
+    project_id: number;
+    registry_id: number;
+    repo_name: string;
+  }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/registries/${pathParams.registry_id}/repositories/${pathParams.repo_name}`;
 });
 
-const getInfra = baseApi<{
-}, {
-  project_id: number,
-}>('GET', pathParams => {
+const getInfra = baseApi<
+  {},
+  {
+    project_id: number;
+  }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/infra`;
 });
 
-const getIngress = baseApi<{
-  cluster_id: number,
-}, { name: string, namespace: string, id: number }>('GET', pathParams => {
+const getIngress = baseApi<
+  {
+    cluster_id: number;
+  },
+  { name: string; namespace: string; id: number }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/k8s/${pathParams.namespace}/ingress/${pathParams.name}`;
 });
 
-const getInvites = baseApi<{}, { id: number }>('GET', pathParams => {
+const getInvites = baseApi<{}, { id: number }>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/invites`;
 });
 
-const getMatchingPods = baseApi<{
-  cluster_id: number,
-  selectors: string[]
-}, { id: number }>('GET', pathParams => {
+const getMatchingPods = baseApi<
+  {
+    cluster_id: number;
+    selectors: string[];
+  },
+  { id: number }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/k8s/pods`;
 });
 
-const getNamespaces = baseApi<{
-  cluster_id: number,
-}, { id: number }>('GET', pathParams => {
+const getNamespaces = baseApi<
+  {
+    cluster_id: number;
+  },
+  { id: number }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/k8s/namespaces`;
 });
 
-const getOAuthIds = baseApi<{
-}, {
-  project_id: number,
-}>('GET', pathParams => {
+const getOAuthIds = baseApi<
+  {},
+  {
+    project_id: number;
+  }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/integrations/oauth`;
 });
 
-const getProjectClusters = baseApi<{}, { id: number }>('GET', pathParams => {
+const getProjectClusters = baseApi<{}, { id: number }>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/clusters`;
 });
 
-const getProjectRegistries = baseApi<{}, { id: number }>('GET', pathParams => {
-  return `/api/projects/${pathParams.id}/registries`;
-});
+const getProjectRegistries = baseApi<{}, { id: number }>(
+  "GET",
+  (pathParams) => {
+    return `/api/projects/${pathParams.id}/registries`;
+  }
+);
 
-const getProjectRepos = baseApi<{}, { id: number }>('GET', pathParams => {
+const getProjectRepos = baseApi<{}, { id: number }>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/repos`;
 });
 
-const getProjects = baseApi<{}, { id: number }>('GET', pathParams => {
+const getProjects = baseApi<{}, { id: number }>("GET", (pathParams) => {
   return `/api/users/${pathParams.id}/projects`;
 });
 
-const getRegistryIntegrations = baseApi('GET', '/api/integrations/registry');
+const getRegistryIntegrations = baseApi("GET", "/api/integrations/registry");
 
-const getReleaseToken = baseApi<{ 
-  namespace: string,
-  cluster_id: number,
-  storage: StorageType,
-}, { name: string, id: number }>('GET', pathParams => {
+const getReleaseToken = baseApi<
+  {
+    namespace: string;
+    cluster_id: number;
+    storage: StorageType;
+  },
+  { name: string; id: number }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/webhook_token`;
 });
 
-const destroyEKS = baseApi<{
-  eks_name: string,
-}, {
-  project_id: number,
-  infra_id: number,
-}>('POST', pathParams => {
+const destroyEKS = baseApi<
+  {
+    eks_name: string;
+  },
+  {
+    project_id: number;
+    infra_id: number;
+  }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/eks/destroy`;
 });
 
-const destroyGKE = baseApi<{
-  gke_name: string,
-}, {
-  project_id: number,
-  infra_id: number,
-}>('POST', pathParams => {
+const destroyGKE = baseApi<
+  {
+    gke_name: string;
+  },
+  {
+    project_id: number;
+    infra_id: number;
+  }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/gke/destroy`;
 });
 
-const destroyDOKS = baseApi<{
-  doks_name: string,
-}, {
-  project_id: number,
-  infra_id: number,
-}>('POST', pathParams => {
+const destroyDOKS = baseApi<
+  {
+    doks_name: string;
+  },
+  {
+    project_id: number;
+    infra_id: number;
+  }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/infra/${pathParams.infra_id}/doks/destroy`;
 });
 
-const getRepoIntegrations = baseApi('GET', '/api/integrations/repo');
+const getRepoIntegrations = baseApi("GET", "/api/integrations/repo");
 
-const getRepos = baseApi<{}, { id: number }>('GET', pathParams => {
+const getRepos = baseApi<{}, { id: number }>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/repos`;
 });
 
-const getRevisions = baseApi<{
-  namespace: string,
-  cluster_id: number,
-  storage: StorageType
-}, { id: number, name: string }>('GET', pathParams => {
+const getRevisions = baseApi<
+  {
+    namespace: string;
+    cluster_id: number;
+    storage: StorageType;
+  },
+  { id: number; name: string }
+>("GET", (pathParams) => {
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/history`;
 });
 
-const getTemplateInfo = baseApi<{}, { name: string, version: string }>('GET', pathParams => {
-  return `/api/templates/${pathParams.name}/${pathParams.version}`;
-});
+const getTemplateInfo = baseApi<{}, { name: string; version: string }>(
+  "GET",
+  (pathParams) => {
+    return `/api/templates/${pathParams.name}/${pathParams.version}`;
+  }
+);
 
-const getTemplates = baseApi('GET', '/api/templates');
+const getTemplates = baseApi("GET", "/api/templates");
 
-const getUser = baseApi<{}, { id: number }>('GET', pathParams => {
+const getUser = baseApi<{}, { id: number }>("GET", (pathParams) => {
   return `/api/users/${pathParams.id}`;
 });
 
-const linkGithubProject = baseApi<{
-}, {
-  project_id: number,
-}>('GET', pathParams => {
+const linkGithubProject = baseApi<
+  {},
+  {
+    project_id: number;
+  }
+>("GET", (pathParams) => {
   return `/api/oauth/projects/${pathParams.project_id}/github`;
 });
 
 const logInUser = baseApi<{
-  email: string,
-  password: string
-}>('POST', '/api/login');
-
-const logOutUser = baseApi('POST', '/api/logout');
-
-const provisionECR = baseApi<{
-  ecr_name: string,
-  aws_integration_id: string,
-}, { id: number }>('POST', pathParams => {
+  email: string;
+  password: string;
+}>("POST", "/api/login");
+
+const logOutUser = baseApi("POST", "/api/logout");
+
+const provisionECR = baseApi<
+  {
+    ecr_name: string;
+    aws_integration_id: string;
+  },
+  { id: number }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.id}/provision/ecr`;
 });
 
-const provisionEKS = baseApi<{
-  eks_name: string,
-  aws_integration_id: string,
-}, { id: number }>('POST', pathParams => {
+const provisionEKS = baseApi<
+  {
+    eks_name: string;
+    aws_integration_id: string;
+  },
+  { id: number }
+>("POST", (pathParams) => {
   return `/api/projects/${pathParams.id}/provision/eks`;
 });
 
-const registerUser = baseApi<{ 
-  email: string,
-  password: string
-}>('POST', '/api/users');
-
-const rollbackChart = baseApi<{
-  namespace: string,
-  storage: StorageType,
-  revision: number
-}, {
-  id: number,
-  name: string,
-  cluster_id: number,
-}>('POST', pathParams => {
+const registerUser = baseApi<{
+  email: string;
+  password: string;
+}>("POST", "/api/users");
+
+const rollbackChart = baseApi<
+  {
+    namespace: string;
+    storage: StorageType;
+    revision: number;
+  },
+  {
+    id: number;
+    name: string;
+    cluster_id: number;
+  }
+>("POST", (pathParams) => {
   let { id, name, cluster_id } = pathParams;
   return `/api/projects/${id}/releases/${name}/rollback?cluster_id=${cluster_id}`;
 });
 
-const uninstallTemplate = baseApi<{
-}, {
-  id: number,
-  name: string, 
-  cluster_id: number,
-  namespace: string,
-  storage: StorageType,
-}>('POST', pathParams => {
+const uninstallTemplate = baseApi<
+  {},
+  {
+    id: number;
+    name: string;
+    cluster_id: number;
+    namespace: string;
+    storage: StorageType;
+  }
+>("POST", (pathParams) => {
   let { id, name, cluster_id, storage, namespace } = pathParams;
   return `/api/projects/${id}/deploy/${name}?cluster_id=${cluster_id}&namespace=${namespace}&storage=${storage}`;
 });
 
-const updateUser = baseApi<{
-  rawKubeConfig?: string,
-  allowedContexts?: string[]
-}, { id: number }>('PUT', pathParams => {
+const updateUser = baseApi<
+  {
+    rawKubeConfig?: string;
+    allowedContexts?: string[];
+  },
+  { id: number }
+>("PUT", (pathParams) => {
   return `/api/users/${pathParams.id}`;
 });
 
-const upgradeChartValues = baseApi<{
-  namespace: string,
-  storage: StorageType,
-  values: string
-}, {
-  id: number,
-  name: string,
-  cluster_id: number,
-}>('POST', pathParams => {
+const upgradeChartValues = baseApi<
+  {
+    namespace: string;
+    storage: StorageType;
+    values: string;
+  },
+  {
+    id: number;
+    name: string;
+    cluster_id: number;
+  }
+>("POST", (pathParams) => {
   let { id, name, cluster_id } = pathParams;
   return `/api/projects/${id}/releases/${name}/upgrade?cluster_id=${cluster_id}`;
 });
@@ -495,4 +610,4 @@ export default {
   uninstallTemplate,
   updateUser,
   upgradeChartValues,
-}
+};

+ 63 - 52
dashboard/src/shared/baseApi.tsx

@@ -1,66 +1,77 @@
-import axios from 'axios';
-import qs from 'qs';
+import axios from "axios";
+import qs from "qs";
 
 // axios.defaults.timeout = 10000;
 
 // Partial function that accepts a generic params type and returns an api method
-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) => {
-
+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
+  ) => {
     // Generate endpoint literal
     let endpointString: ((pathParams: S) => string) | string;
-    if (typeof endpoint === 'string') {
+    if (typeof endpoint === "string") {
       endpointString = endpoint;
     } else {
       endpointString = endpoint(pathParams);
     }
 
     // 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);
-      });
-    } else if (requestType === 'PUT') {
-      axios.put(endpointString, params, {
-        headers: {
-          Authorization: `Bearer ${token}`
-        }
-      })
-      .then(res => {
-        callback && callback(null, res);
-      })
-      .catch(err => {
-        callback && callback(err, null);
-      });
-    } else if (requestType === 'DELETE') {
-      axios.delete(endpointString, params)
-      .then(res => {
-        callback && callback(null, res);
-      })
-      .catch(err => {
-        callback && callback(err, null);
-      })
+    if (requestType === "POST") {
+      axios
+        .post(endpointString, params, {
+          headers: {
+            Authorization: `Bearer ${token}`,
+          },
+        })
+        .then((res) => {
+          callback && callback(null, res);
+        })
+        .catch((err) => {
+          callback && callback(err, null);
+        });
+    } else if (requestType === "PUT") {
+      axios
+        .put(endpointString, params, {
+          headers: {
+            Authorization: `Bearer ${token}`,
+          },
+        })
+        .then((res) => {
+          callback && callback(null, res);
+        })
+        .catch((err) => {
+          callback && callback(err, null);
+        });
+    } else if (requestType === "DELETE") {
+      axios
+        .delete(endpointString, params)
+        .then((res) => {
+          callback && callback(null, res);
+        })
+        .catch((err) => {
+          callback && callback(err, null);
+        });
     } 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);
-      });
+      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);
+        });
     }
-  }
-}
+  };
+};

+ 70 - 64
dashboard/src/shared/common.tsx

@@ -1,69 +1,76 @@
-import aws from 'assets/aws.png';
-import digitalOcean from 'assets/do.png';
-import gcp from 'assets/gcp.png';
-import { InfraType } from 'shared/types';
+import aws from "assets/aws.png";
+import digitalOcean from "assets/do.png";
+import gcp from "assets/gcp.png";
+import { InfraType } from "shared/types";
 
 export const infraNames: any = {
-  'ecr': 'Elastic Container Registry (ECR)',
-  'eks': 'Elastic Kubernetes Service (EKS)',
-  'gcr': 'Google Container Registry (GCR)',
-  'gke': 'Google Kubernetes Engine (GKE)',
-  'docr': 'Digital Ocean Container Registry',
-  'doks': 'Digital Ocean Kubernetes Service'
+  ecr: "Elastic Container Registry (ECR)",
+  eks: "Elastic Kubernetes Service (EKS)",
+  gcr: "Google Container Registry (GCR)",
+  gke: "Google Kubernetes Engine (GKE)",
+  docr: "Digital Ocean Container Registry",
+  doks: "Digital Ocean Kubernetes Service",
 };
 
 export const integrationList: any = {
-  'kubernetes': {
-    icon: 'https://uxwing.com/wp-content/themes/uxwing/download/10-brands-and-social-media/kubernetes.png',
-    label: 'Kubernetes',
-    buttonText: 'Add a Cluster',
+  kubernetes: {
+    icon:
+      "https://uxwing.com/wp-content/themes/uxwing/download/10-brands-and-social-media/kubernetes.png",
+    label: "Kubernetes",
+    buttonText: "Add a Cluster",
   },
-  'repo': {
-    icon: 'https://3.bp.blogspot.com/-xhNpNJJyQhk/XIe4GY78RQI/AAAAAAAAItc/ouueFUj2Hqo5dntmnKqEaBJR4KQ4Q2K3ACK4BGAYYCw/s1600/logo%2Bgit%2Bicon.png',
-    label: 'Git Repository',
-    buttonText: 'Add a Repository',
+  repo: {
+    icon:
+      "https://3.bp.blogspot.com/-xhNpNJJyQhk/XIe4GY78RQI/AAAAAAAAItc/ouueFUj2Hqo5dntmnKqEaBJR4KQ4Q2K3ACK4BGAYYCw/s1600/logo%2Bgit%2Bicon.png",
+    label: "Git Repository",
+    buttonText: "Add a Repository",
   },
-  'registry': {
-    icon: 'https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png',
-    label: 'Docker Registry',
-    buttonText: 'Add a Registry',
+  registry: {
+    icon:
+      "https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png",
+    label: "Docker Registry",
+    buttonText: "Add a Registry",
   },
-  'gke': {
-    icon: 'https://sysdig.com/wp-content/uploads/2016/08/GKE_color.png',
-    label: 'Google Kubernetes Engine (GKE)',
+  gke: {
+    icon: "https://sysdig.com/wp-content/uploads/2016/08/GKE_color.png",
+    label: "Google Kubernetes Engine (GKE)",
   },
-  'eks': {
-    icon: 'https://img.stackshare.io/service/7991/amazon-eks.png',
-    label: 'Amazon Elastic Kubernetes Service (EKS)',
+  eks: {
+    icon: "https://img.stackshare.io/service/7991/amazon-eks.png",
+    label: "Amazon Elastic Kubernetes Service (EKS)",
   },
-  'kube': {
-    icon: 'https://uxwing.com/wp-content/themes/uxwing/download/10-brands-and-social-media/kubernetes.png',
-    label: 'Upload Kubeconfig'
+  kube: {
+    icon:
+      "https://uxwing.com/wp-content/themes/uxwing/download/10-brands-and-social-media/kubernetes.png",
+    label: "Upload Kubeconfig",
   },
-  'docker': {
-    icon: 'https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png',
-    label: 'Docker Hub',
+  docker: {
+    icon:
+      "https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png",
+    label: "Docker Hub",
   },
-  'gcr': {
-    icon: 'https://carlossanchez.files.wordpress.com/2019/06/21046548.png?w=640',
-    label: 'Google Container Registry (GCR)',
+  gcr: {
+    icon:
+      "https://carlossanchez.files.wordpress.com/2019/06/21046548.png?w=640",
+    label: "Google Container Registry (GCR)",
   },
-  'ecr': {
-    icon: 'https://avatars2.githubusercontent.com/u/52505464?s=400&u=da920f994c67665c7ad6c606a5286557d4f8555f&v=4',
-    label: 'Elastic Container Registry (ECR)',
+  ecr: {
+    icon:
+      "https://avatars2.githubusercontent.com/u/52505464?s=400&u=da920f994c67665c7ad6c606a5286557d4f8555f&v=4",
+    label: "Elastic Container Registry (ECR)",
   },
-  'aws': {
+  aws: {
     icon: aws,
-    label: 'AWS',
+    label: "AWS",
   },
-  'gcp': {
+  gcp: {
     icon: gcp,
-    label: 'GCP',
+    label: "GCP",
   },
-  'do': {
+  do: {
     icon: digitalOcean,
-    label: 'DigitalOcean',
-  }
+    label: "DigitalOcean",
+  },
 };
 
 export const isAlphanumeric = (x: string | null) => {
@@ -72,20 +79,20 @@ export const isAlphanumeric = (x: string | null) => {
     return false;
   }
   return true;
-}
+};
 
 export const getIgnoreCase = (object: any, key: string) => {
-  return object[Object.keys(object)
-    .find(k => k.toLowerCase() === key.toLowerCase())
+  return object[
+    Object.keys(object).find((k) => k.toLowerCase() === key.toLowerCase())
   ];
-}
+};
 
 export const includesCompletedInfraSet = (infras: InfraType[]): boolean => {
   // TODO: declare globally while avoidiing changes to the array on helper call
   let infraSets = [
-    ['ecr', 'eks'],
-    ['gcr', 'gke'],
-    ['docr', 'doks']
+    ["ecr", "eks"],
+    ["gcr", "gke"],
+    ["docr", "doks"],
   ];
   if (infras.length === 0) {
     return false;
@@ -93,7 +100,7 @@ export const includesCompletedInfraSet = (infras: InfraType[]): boolean => {
 
   let completed = [] as string[];
   infras.forEach((infra: InfraType, i: number) => {
-    if (infra.status === 'created') {
+    if (infra.status === "created") {
       completed.push(infra.kind);
     }
   });
@@ -109,26 +116,25 @@ export const includesCompletedInfraSet = (infras: InfraType[]): boolean => {
     if (infraSet.length === 0) {
       anyCompleted = true;
     }
-  })
+  });
   return anyCompleted;
-}
+};
 
 export const filterOldInfras = (infras: InfraType[]): InfraType[] => {
   let infraSets = [
-    ['ecr', 'eks'],
-    ['gcr', 'gke'],
-    ['docr', 'doks']
+    ["ecr", "eks"],
+    ["gcr", "gke"],
+    ["docr", "doks"],
   ];
   let newestInstances = {} as any;
   let newestId = -1;
   let whitelistedInfras = [] as string[];
   infras.forEach((infra: InfraType, i: number) => {
-
     // Determine the most recent set for which provisioning was attempted
     if (infra.id > newestId) {
       newestId = infra.id;
       infraSets.forEach((infraSet: string[]) => {
-        infraSet.includes(infra.kind) ? whitelistedInfras = infraSet : null;
+        infraSet.includes(infra.kind) ? (whitelistedInfras = infraSet) : null;
       });
     }
 
@@ -144,7 +150,7 @@ export const filterOldInfras = (infras: InfraType[]): InfraType[] => {
 
   let newestInfras = Object.values(newestInstances) as InfraType[];
   let result = newestInfras.filter((x: InfraType) => {
-    return whitelistedInfras.includes(x.kind)
+    return whitelistedInfras.includes(x.kind);
   });
   return result;
-}
+};

+ 26 - 18
dashboard/src/shared/feedback.tsx

@@ -1,19 +1,27 @@
-import axios from 'axios';
+import axios from "axios";
 
-export const handleSubmitFeedback = (msg: string, callback?: (err: any, res: any) => void) => {
-  axios.post(process.env.FEEDBACK_ENDPOINT, {
-    key: process.env.DISCORD_KEY,
-    cid: process.env.DISCORD_CID,
-    message: msg,
-  }, {
-    headers: {
-      Authorization: `Bearer <>`
-    }
-  })
-  .then(res => {
-    callback && callback(null, res);
-  })
-  .catch(err => {
-    callback && callback(err, null);
-  });
-}
+export const handleSubmitFeedback = (
+  msg: string,
+  callback?: (err: any, res: any) => void
+) => {
+  axios
+    .post(
+      process.env.FEEDBACK_ENDPOINT,
+      {
+        key: process.env.DISCORD_KEY,
+        cid: process.env.DISCORD_CID,
+        message: msg,
+      },
+      {
+        headers: {
+          Authorization: `Bearer <>`,
+        },
+      }
+    )
+    .then((res) => {
+      callback && callback(null, res);
+    })
+    .catch((err) => {
+      callback && callback(err, null);
+    });
+};

+ 1 - 1
dashboard/src/shared/images.d.ts

@@ -16,4 +16,4 @@ declare module "*.png" {
 declare module "*.svg" {
   const value: any;
   export = value;
-}
+}

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

@@ -1 +1 @@
-export const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+export const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

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

@@ -1,21 +1,21 @@
-export const kindToIcon: any = {
-  'Deployment': 'category',
-  'Pod': 'fiber_manual_record',
-  'Service': 'alt_route',
-  'Ingress': 'sensor_door',
-  'StatefulSet': 'location_city',
-  'Secret': 'vpn_key',
-  'ServiceAccount': 'home_repair_service',
-  'ClusterRole': 'person',
-  'ClusterRoleBinding': 'swap_horiz',
-  'Role': 'portrait',
-  'RoleBinding': 'swap_horizontal_circle',
-  'ConfigMap': 'map',
-  'PodSecurityPolicy': 'security'
-}
+export const kindToIcon: { [kind: string]: string } = {
+  Deployment: "category",
+  Pod: "fiber_manual_record",
+  Service: "alt_route",
+  Ingress: "sensor_door",
+  StatefulSet: "location_city",
+  Secret: "vpn_key",
+  ServiceAccount: "home_repair_service",
+  ClusterRole: "person",
+  ClusterRoleBinding: "swap_horiz",
+  Role: "portrait",
+  RoleBinding: "swap_horizontal_circle",
+  ConfigMap: "map",
+  PodSecurityPolicy: "security",
+};
 
-export const edgeColors: any = {
-  'LabelRel': '#32a85f',
-  'ControlRel': '#fcb603',
-  'SpecRel': '#949EFF'
-};
+export const edgeColors: { [kind: string]: string } = {
+  LabelRel: "#32a85f",
+  ControlRel: "#fcb603",
+  SpecRel: "#949EFF",
+};

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

@@ -1,168 +1,168 @@
 export interface ClusterType {
-  id: number,
-  name: string,
-  server: string,
-  service_account_id: number
-  infra_id?: number,
-  service?: string,
+  id: number;
+  name: string;
+  server: string;
+  service_account_id: number;
+  infra_id?: number;
+  service?: string;
 }
 
 export interface ChartType {
-  name: string,
+  name: string;
   info: {
-    last_deployed: string,
-    deleted: string,
-    description: string,
-    status: string
-  },
+    last_deployed: string;
+    deleted: string;
+    description: string;
+    status: string;
+  };
   chart: {
     metadata: {
-      name: string,
-      home: string,
-      sources: string,
-      version: string,
-      description: string,
-      icon: string,
-      apiVersion: string
-    },
+      name: string;
+      home: string;
+      sources: string;
+      version: string;
+      description: string;
+      icon: string;
+      apiVersion: string;
+    };
     files?: {
-      data: string,
-      name: string,
-    }[],
-  },
-  form?: FormYAML,
-  config: any,
-  version: number,
-  namespace: string
+      data: string;
+      name: string;
+    }[];
+  };
+  form?: FormYAML;
+  config: any;
+  version: number;
+  namespace: string;
 }
 
 export interface ResourceType {
-  ID: number,
-  Kind: string,
-  Name: string,
-  RawYAML: any,
-  Relations: any
+  ID: number;
+  Kind: string;
+  Name: string;
+  RawYAML: any;
+  Relations: any;
 }
 
 export interface NodeType {
-  id: number,
-  name: string,
-  kind: string,
-  RawYAML?: any,
-  x: number,
-  y: number,
-  w: number,
-  h: number,
-  toCursorX?: number,
-  toCursorY?: number
+  id: number;
+  name: string;
+  kind: string;
+  RawYAML?: any;
+  x: number;
+  y: number;
+  w: number;
+  h: number;
+  toCursorX?: number;
+  toCursorY?: number;
 }
 
 export interface EdgeType {
-  type: string,
-  source: number,
-  target: number
+  type: string;
+  source: number;
+  target: number;
 }
 
 export enum StorageType {
-  Secret = 'secret',
-  ConfigMap = 'configmap',
-  Memory = 'memory'
+  Secret = "secret",
+  ConfigMap = "configmap",
+  Memory = "memory",
 }
 
 // PorterTemplate represents a bundled Porter template
 export interface PorterTemplate {
-  name: string,
-  version: string,
-  description: string,
-  icon: string,
+  name: string;
+  version: string;
+  description: string;
+  icon: string;
 }
 
 // FormYAML represents a chart's values.yaml form abstraction
 export interface FormYAML {
-	name?: string,  
-	icon?: string,   
-	description?: string,   
-  tags?: string[],
+  name?: string;
+  icon?: string;
+  description?: string;
+  tags?: string[];
   tabs?: {
-    name: string,
-    label: string,
-    sections?: Section[]
-  }[],
+    name: string;
+    label: string;
+    sections?: Section[];
+  }[];
 }
 
 export interface Section {
-  name?: string,
-  show_if?: string,
-  contents: FormElement[]
+  name?: string;
+  show_if?: string;
+  contents: FormElement[];
 }
 
 // FormElement represents a form element
 export interface FormElement {
-  type: string,
-  label: string,
-  required?: boolean,
-  name?: string,
-  variable?: string,
-  value?: any,
+  type: string;
+  label: string;
+  required?: boolean;
+  name?: string;
+  variable?: string;
+  value?: any;
   settings?: {
-    default?: number | string | boolean,
-    options?: any[],
-    unit?: string
-  }
+    default?: number | string | boolean;
+    options?: any[];
+    unit?: string;
+  };
 }
 
 export interface RepoType {
-  FullName: string,
-  kind: string,
-  GHRepoID: number,
+  FullName: string;
+  kind: string;
+  GHRepoID: number;
 }
 
 export interface FileType {
-  Path: string,
-  Type: string
+  Path: string;
+  Type: string;
 }
 
 export interface ProjectType {
-  id: number,
-  name: string,
+  id: number;
+  name: string;
   roles: {
-    id: number,
-    kind: string,
-    user_id: number,
-    project_id: number
-  }[]
+    id: number;
+    kind: string;
+    user_id: number;
+    project_id: number;
+  }[];
 }
 
 export interface ChoiceType {
-  value: string,
-  label: string
+  value: string;
+  label: string;
 }
 
 export interface ImageType {
-  kind: string,
-  source: string,
-  registryId: number,
-  name: string,
+  kind: string;
+  source: string;
+  registryId: number;
+  name: string;
 }
 
 export interface InfraType {
-  id: number,
-  project_id: number,
-  kind: string,
-  status: string,
+  id: number;
+  project_id: number;
+  kind: string;
+  status: string;
 }
 
 export interface InviteType {
-  token: string,
-  expired: boolean,
-  email: string,
-  accepted: boolean,
-  id: number,
+  token: string;
+  expired: boolean;
+  email: string;
+  accepted: boolean;
+  id: number;
 }
 
 export interface ActionConfigType {
-  git_repo: string,
-  image_repo_uri: string,
-  git_repo_id: number,
-  dockerfile_path: string,
-}
+  git_repo: string;
+  image_repo_uri: string;
+  git_repo_id: number;
+  dockerfile_path: string;
+}