Просмотр исходного кода

email verification logout handling + frontend & fixes #419

jusrhee 5 лет назад
Родитель
Сommit
e1256fad64
27 измененных файлов с 368 добавлено и 285 удалено
  1. 1 1
      dashboard/src/components/InfoTooltip.tsx
  2. 1 1
      dashboard/src/components/RadioSelector.tsx
  3. 2 2
      dashboard/src/components/ResourceTab.tsx
  4. 5 4
      dashboard/src/components/SaveButton.tsx
  5. 2 2
      dashboard/src/components/Selector.tsx
  6. 1 1
      dashboard/src/components/TabRegion.tsx
  7. 1 1
      dashboard/src/components/TooltipParent.tsx
  8. 2 1
      dashboard/src/components/image-selector/ImageSelector.tsx
  9. 2 2
      dashboard/src/main/CurrentError.tsx
  10. 33 11
      dashboard/src/main/Main.tsx
  11. 80 66
      dashboard/src/main/auth/ResetPasswordFinalize.tsx
  12. 29 24
      dashboard/src/main/auth/ResetPasswordInit.tsx
  13. 76 27
      dashboard/src/main/auth/VerifyEmail.tsx
  14. 5 9
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx
  15. 3 3
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx
  16. 6 3
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx
  17. 1 12
      dashboard/src/main/home/navbar/Navbar.tsx
  18. 2 2
      dashboard/src/shared/Context.tsx
  19. 3 3
      dashboard/src/shared/ansiparser.tsx
  20. 65 62
      dashboard/src/shared/api.tsx
  21. 6 6
      dashboard/src/shared/baseApi.tsx
  22. 17 17
      dashboard/src/shared/common.tsx
  23. 5 5
      dashboard/src/shared/feedback.tsx
  24. 2 2
      dashboard/src/shared/rosettaStone.tsx
  25. 2 2
      dashboard/src/shared/routing.tsx
  26. 1 1
      dashboard/src/shared/types.tsx
  27. 15 15
      dashboard/webpack.config.js

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 2 - 1
dashboard/src/components/image-selector/ImageSelector.tsx

@@ -192,6 +192,7 @@ export default class ImageSelector extends Component<PropsType, StateType> {
       <Label>
       <Label>
         <img src={icon} />
         <img src={icon} />
         <Input
         <Input
+          autoFocus={true}
           onClick={(e: any) => e.stopPropagation()}
           onClick={(e: any) => e.stopPropagation()}
           value={selectedImageUrl}
           value={selectedImageUrl}
           onChange={(e: any) => {
           onChange={(e: any) => {
@@ -202,7 +203,7 @@ export default class ImageSelector extends Component<PropsType, StateType> {
               this.setState({ isExpanded: true });
               this.setState({ isExpanded: true });
             }
             }
           }}
           }}
-          placeholder="Enter or select your container image URL"
+          placeholder="Type your container image URL here (or select below)"
         />
         />
       </Label>
       </Label>
     );
     );

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

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

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

@@ -29,7 +29,7 @@ export default class Main extends Component<PropsType, StateType> {
     loading: true,
     loading: true,
     isLoggedIn: false,
     isLoggedIn: false,
     isEmailVerified: false,
     isEmailVerified: false,
-    initialized: localStorage.getItem("init") === "true"
+    initialized: localStorage.getItem("init") === "true",
   };
   };
 
 
   componentDidMount() {
   componentDidMount() {
@@ -39,20 +39,20 @@ export default class Main extends Component<PropsType, StateType> {
     error && setCurrentError(error);
     error && setCurrentError(error);
     api
     api
       .checkAuth("", {}, {})
       .checkAuth("", {}, {})
-      .then(res => {
+      .then((res) => {
         if (res && res.data) {
         if (res && res.data) {
           setUser(res?.data?.id, res?.data?.email);
           setUser(res?.data?.id, res?.data?.email);
           this.setState({
           this.setState({
             isLoggedIn: true,
             isLoggedIn: true,
             isEmailVerified: res?.data?.email_verified,
             isEmailVerified: res?.data?.email_verified,
             initialized: true,
             initialized: true,
-            loading: false
+            loading: false,
           });
           });
         } else {
         } else {
           this.setState({ isLoggedIn: false, loading: false });
           this.setState({ isLoggedIn: false, loading: false });
         }
         }
       })
       })
-      .catch(err => this.setState({ isLoggedIn: false, loading: false }));
+      .catch((err) => this.setState({ isLoggedIn: false, loading: false }));
   }
   }
 
 
   initialize = () => {
   initialize = () => {
@@ -61,15 +61,37 @@ export default class Main extends Component<PropsType, StateType> {
   };
   };
 
 
   authenticate = () => {
   authenticate = () => {
-    this.setState({ isLoggedIn: true, initialized: true });
+    api
+      .checkAuth("", {}, {})
+      .then((res) => {
+        if (res && res.data) {
+          this.context.setUser(res?.data?.id, res?.data?.email);
+          this.setState({
+            isLoggedIn: true,
+            isEmailVerified: res?.data?.email_verified,
+            initialized: true,
+            loading: false,
+          });
+        } else {
+          this.setState({ isLoggedIn: false, loading: false });
+        }
+      })
+      .catch((err) => this.setState({ isLoggedIn: false, loading: false }));
   };
   };
 
 
   handleLogOut = () => {
   handleLogOut = () => {
     // Clears local storage for proper rendering of clusters
     // Clears local storage for proper rendering of clusters
-    localStorage.clear();
-
-    this.context.clearContext();
-    this.setState({ isLoggedIn: false, initialized: true });
+    // Attempt user logout
+    api
+      .logOutUser("<token>", {}, {})
+      .then(() => {
+        this.context.clearContext();
+        this.setState({ isLoggedIn: false, initialized: true });
+        localStorage.clear();
+      })
+      .catch((err) =>
+        this.context.setCurrentError(err.response.data.errors[0])
+      );
   };
   };
 
 
   renderMain = () => {
   renderMain = () => {
@@ -84,7 +106,7 @@ export default class Main extends Component<PropsType, StateType> {
           <Route
           <Route
             path="/"
             path="/"
             render={() => {
             render={() => {
-              return <VerifyEmail />;
+              return <VerifyEmail handleLogout={this.handleLogOut} />;
             }}
             }}
           />
           />
         </Switch>
         </Switch>
@@ -146,7 +168,7 @@ export default class Main extends Component<PropsType, StateType> {
         />
         />
         <Route
         <Route
           path={`/:baseRoute`}
           path={`/:baseRoute`}
-          render={routeProps => {
+          render={(routeProps) => {
             const baseRoute = routeProps.match.params.baseRoute;
             const baseRoute = routeProps.match.params.baseRoute;
             if (
             if (
               this.state.isLoggedIn &&
               this.state.isLoggedIn &&

+ 80 - 66
dashboard/src/main/auth/ResetPasswordFinalize.tsx

@@ -4,7 +4,7 @@ import logo from "assets/logo.png";
 
 
 import api from "shared/api";
 import api from "shared/api";
 import { Context } from "shared/Context";
 import { Context } from "shared/Context";
-import Loading from "components/Loading"
+import Loading from "components/Loading";
 
 
 type PropsType = {};
 type PropsType = {};
 
 
@@ -26,7 +26,7 @@ export default class ResetPasswordInit extends Component<PropsType, StateType> {
     password: "",
     password: "",
     token_id: 0,
     token_id: 0,
     passwordError: false,
     passwordError: false,
-    tokenError: false, 
+    tokenError: false,
     loading: true,
     loading: true,
     submitted: false,
     submitted: false,
   };
   };
@@ -43,25 +43,27 @@ export default class ResetPasswordInit extends Component<PropsType, StateType> {
     let tokenIDFromParams = urlParams.get("token_id");
     let tokenIDFromParams = urlParams.get("token_id");
 
 
     api
     api
-    .createPasswordResetVerify(
-      "",
-      {
-        email: emailFromParam,
-        token: tokenFromParams,
-        token_id: parseInt(tokenIDFromParams),
-      },
-      {}
-    )
-    .then(() => {
-        this.setState({ loading: false })
-    })
-    .catch((err) =>
-      this.setState({ loading: false, tokenError: true })
-    );
+      .createPasswordResetVerify(
+        "",
+        {
+          email: emailFromParam,
+          token: tokenFromParams,
+          token_id: parseInt(tokenIDFromParams),
+        },
+        {}
+      )
+      .then(() => {
+        this.setState({ loading: false });
+      })
+      .catch((err) => this.setState({ loading: false, tokenError: true }));
 
 
     document.addEventListener("keydown", this.handleKeyDown);
     document.addEventListener("keydown", this.handleKeyDown);
 
 
-    this.setState({ email: emailFromParam, token: tokenFromParams, token_id: parseInt(tokenIDFromParams) })
+    this.setState({
+      email: emailFromParam,
+      token: tokenFromParams,
+      token_id: parseInt(tokenIDFromParams),
+    });
   }
   }
 
 
   componentWillUnmount() {
   componentWillUnmount() {
@@ -85,64 +87,76 @@ export default class ResetPasswordInit extends Component<PropsType, StateType> {
 
 
     // Call reset password
     // Call reset password
     api
     api
-    .createPasswordResetFinalize(
-      "",
-      {
-        email: email,
-        token: token,
-        token_id: token_id,
-        new_password: password,
-      },
-      {}
-    )
-    .then((res) => {
+      .createPasswordResetFinalize(
+        "",
+        {
+          email: email,
+          token: token,
+          token_id: token_id,
+          new_password: password,
+        },
+        {}
+      )
+      .then((res) => {
         // redirect to dashboard with message after timeout
         // redirect to dashboard with message after timeout
-        this.setState({ submitted: true })
+        this.setState({ submitted: true });
 
 
         setTimeout(() => {
         setTimeout(() => {
-            window.location.href = "/login"
-        }, 2000)
-    })
-    .catch((err) =>
-      this.setState({ tokenError: true })
-    );
+          window.location.href = "/login";
+        }, 2000);
+      })
+      .catch((err) => this.setState({ tokenError: true }));
   };
   };
 
 
   render() {
   render() {
-    let { password, passwordError, submitted, loading, tokenError } = this.state;
-
-    let inputSection = <div>
+    let {
+      password,
+      passwordError,
+      submitted,
+      loading,
+      tokenError,
+    } = this.state;
+
+    let inputSection = (
+      <div>
         <InputWrapper>
         <InputWrapper>
-              <Input
-                type="password"
-                placeholder="Password"
-                value={password}
-                onChange={(e: ChangeEvent<HTMLInputElement>) =>
-                  this.setState({
-                    password: e.target.value,
-                    passwordError: false,
-                  })
-                }
-                valid={!passwordError}
-              />
-              {this.renderPasswordError()}
-            </InputWrapper>
-            <Button onClick={this.handleResetPasswordFinalize}>Continue</Button>
-    </div>
+          <Input
+            type="password"
+            placeholder="Password"
+            value={password}
+            onChange={(e: ChangeEvent<HTMLInputElement>) =>
+              this.setState({
+                password: e.target.value,
+                passwordError: false,
+              })
+            }
+            valid={!passwordError}
+          />
+          {this.renderPasswordError()}
+        </InputWrapper>
+        <Button onClick={this.handleResetPasswordFinalize}>Continue</Button>
+      </div>
+    );
 
 
     if (loading) {
     if (loading) {
-        inputSection = <StatusText>
-            <Loading />
+      inputSection = (
+        <StatusText>
+          <Loading />
         </StatusText>
         </StatusText>
+      );
     } else if (tokenError) {
     } else if (tokenError) {
-        inputSection = <StatusText>
-            Link has already been used or has expired. Please 
-            <Link href="/password/reset">try again.</Link>
+      inputSection = (
+        <StatusText>
+          Link has already been used or has expired. Please
+          <Link href="/password/reset">try again.</Link>
         </StatusText>
         </StatusText>
+      );
     } else if (submitted) {
     } else if (submitted) {
-        inputSection = <StatusText>
-            Password changed successfully! Redirecting to login...
+      inputSection = (
+        <StatusText>
+          Password changed successfully! Redirecting to login...
         </StatusText>
         </StatusText>
+      );
     }
     }
 
 
     return (
     return (
@@ -348,10 +362,10 @@ const Prompt = styled.div`
 `;
 `;
 
 
 const StatusText = styled.div`
 const StatusText = styled.div`
-padding: 18px 30px; 
-font-family: "Work Sans", sans-serif;
-font-size: 14px;
-line-height: 160%;
+  padding: 18px 30px;
+  font-family: "Work Sans", sans-serif;
+  font-size: 14px;
+  line-height: 160%;
 `;
 `;
 
 
 const Logo = styled.img`
 const Logo = styled.img`

+ 29 - 24
dashboard/src/main/auth/ResetPasswordInit.tsx

@@ -62,7 +62,7 @@ export default class ResetPasswordInit extends Component<PropsType, StateType> {
           {}
           {}
         )
         )
         .then((res) => {
         .then((res) => {
-          this.setState({ submitted: true })
+          this.setState({ submitted: true });
         })
         })
         .catch((err) =>
         .catch((err) =>
           this.context.setCurrentError(err.response.data.errors[0])
           this.context.setCurrentError(err.response.data.errors[0])
@@ -73,29 +73,34 @@ export default class ResetPasswordInit extends Component<PropsType, StateType> {
   render() {
   render() {
     let { email, emailError, submitted } = this.state;
     let { email, emailError, submitted } = this.state;
 
 
-    let formSection = <div>
+    let formSection = (
+      <div>
         <InputWrapper>
         <InputWrapper>
-              <Input
-                type="email"
-                placeholder="Email"
-                value={email}
-                onChange={(e: ChangeEvent<HTMLInputElement>) =>
-                  this.setState({
-                    email: e.target.value,
-                    emailError: false,
-                  })
-                }
-                valid={!emailError}
-              />
-              {this.renderEmailError()}
-            </InputWrapper>
-            <Button onClick={this.handleResetPasswordInit}>Continue</Button>
-    </div>
+          <Input
+            type="email"
+            placeholder="Email"
+            value={email}
+            onChange={(e: ChangeEvent<HTMLInputElement>) =>
+              this.setState({
+                email: e.target.value,
+                emailError: false,
+              })
+            }
+            valid={!emailError}
+          />
+          {this.renderEmailError()}
+        </InputWrapper>
+        <Button onClick={this.handleResetPasswordInit}>Continue</Button>
+      </div>
+    );
 
 
     if (submitted) {
     if (submitted) {
-        formSection = <StatusText>
-            If we found an account matching { email }, we've sent you password reset instructions. Remember to check your spam folder.
+      formSection = (
+        <StatusText>
+          If we found an account matching {email}, we've sent you password reset
+          instructions. Remember to check your spam folder.
         </StatusText>
         </StatusText>
+      );
     }
     }
 
 
     return (
     return (
@@ -308,10 +313,10 @@ const Logo = styled.img`
 `;
 `;
 
 
 const StatusText = styled.div`
 const StatusText = styled.div`
-padding: 18px 30px; 
-font-family: "Work Sans", sans-serif;
-font-size: 14px;
-line-height: 160%;
+  padding: 18px 30px;
+  font-family: "Work Sans", sans-serif;
+  font-size: 14px;
+  line-height: 160%;
 `;
 `;
 
 
 const FormWrapper = styled.div`
 const FormWrapper = styled.div`

+ 76 - 27
dashboard/src/main/auth/VerifyEmail.tsx

@@ -6,7 +6,9 @@ import api from "shared/api";
 import { emailRegex } from "shared/regex";
 import { emailRegex } from "shared/regex";
 import { Context } from "shared/Context";
 import { Context } from "shared/Context";
 
 
-type PropsType = {};
+type PropsType = {
+  handleLogout: () => void;
+};
 
 
 type StateType = {
 type StateType = {
   submitted: boolean;
   submitted: boolean;
@@ -18,32 +20,50 @@ export default class VerifyEmail extends Component<PropsType, StateType> {
   };
   };
 
 
   handleSendEmail = (): void => {
   handleSendEmail = (): void => {
-      api
-        .createEmailVerification("", {}, {})
-        .then((res) => {
-          this.setState({ submitted: true })
-        })
-        .catch((err) =>
-          this.context.setCurrentError(err.response.data.errors[0])
-        );
+    api
+      .createEmailVerification("", {}, {})
+      .then((res) => {
+        this.setState({ submitted: true });
+      })
+      .catch((err) =>
+        this.context.setCurrentError(err.response.data.errors[0])
+      );
   };
   };
 
 
   render() {
   render() {
     let { submitted } = this.state;
     let { submitted } = this.state;
 
 
-    let formSection = <div>
+    let formSection = (
+      <div>
         <InputWrapper>
         <InputWrapper>
-            <StatusText>
-                Please verify your email to continue onto Porter.
-            </StatusText>
-            </InputWrapper>
-            <Button onClick={this.handleSendEmail}>Send Verification Email</Button>
-    </div>
+          <StatusText>A verification email will be sent to</StatusText>
+          <Email>{this.context.user?.email}</Email>
+        </InputWrapper>
+        <StatusText>
+          Proceed below to verify your email and finish setting up your profile
+        </StatusText>
+        <Button onClick={this.handleSendEmail}>Send Verification Email</Button>
+      </div>
+    );
 
 
     if (submitted) {
     if (submitted) {
-        formSection = <StatusText>
-            Please check your inbox for a verification email! Remember to check your spam folder.
-        </StatusText>
+      formSection = (
+        <>
+          <Buffer />
+          <StatusText lessPadding={true}>
+            A verification email was sent to{" "}
+            <White>{this.context.user?.email}</White>
+          </StatusText>
+          <StatusText lessPadding={true}>
+            Check your inbox for a verification email. Don't forget to check
+            your spam folder
+          </StatusText>
+          <StatusText lessPadding={true}>
+            Need help?
+            <Link href="mailto:contact@getporter.dev">Contact us</Link>
+          </StatusText>
+        </>
+      );
     }
     }
 
 
     return (
     return (
@@ -54,9 +74,13 @@ export default class VerifyEmail extends Component<PropsType, StateType> {
           </OverflowWrapper>
           </OverflowWrapper>
           <FormWrapper>
           <FormWrapper>
             <Logo src={logo} />
             <Logo src={logo} />
-            <Prompt>Verify Email</Prompt>
+            <Prompt>Verify Your Email</Prompt>
             <DarkMatter />
             <DarkMatter />
             {formSection}
             {formSection}
+            <Helper>
+              Want to use a different email?
+              <Link onClick={this.props.handleLogout}>Log out</Link>
+            </Helper>
           </FormWrapper>
           </FormWrapper>
         </LoginPanel>
         </LoginPanel>
 
 
@@ -76,6 +100,28 @@ export default class VerifyEmail extends Component<PropsType, StateType> {
 
 
 VerifyEmail.contextType = Context;
 VerifyEmail.contextType = Context;
 
 
+const Buffer = styled.div`
+  width: 100%;
+  height: 20px;
+`;
+
+const White = styled.div`
+  color: white;
+`;
+
+const Email = styled.div`
+  background: #ffffff11;
+  border: 1px solid #ffffff44;
+  border-radius: 3px;
+  font-size: 14px;
+  color: #aaaabb;
+  height: 30px;
+  margin: 0 60px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
 const Footer = styled.div`
 const Footer = styled.div`
   position: absolute;
   position: absolute;
   bottom: 0;
   bottom: 0;
@@ -90,7 +136,7 @@ const Footer = styled.div`
 `;
 `;
 
 
 const DarkMatter = styled.div`
 const DarkMatter = styled.div`
-  margin-top: -10px;
+  margin-top: -20px;
 `;
 `;
 
 
 const Or = styled.div`
 const Or = styled.div`
@@ -144,6 +190,7 @@ const OAuthButton = styled.div`
 const Link = styled.a`
 const Link = styled.a`
   margin-left: 5px;
   margin-left: 5px;
   color: #819bfd;
   color: #819bfd;
+  cursor: pointer;
 `;
 `;
 
 
 const Helper = styled.div`
 const Helper = styled.div`
@@ -247,15 +294,17 @@ const Prompt = styled.div`
 const Logo = styled.img`
 const Logo = styled.img`
   width: 140px;
   width: 140px;
   margin-top: 50px;
   margin-top: 50px;
-  margin-bottom: 75px;
+  margin-bottom: 60px;
   user-select: none;
   user-select: none;
 `;
 `;
 
 
-const StatusText = styled.div`
-padding: 18px 30px; 
-font-family: "Work Sans", sans-serif;
-font-size: 14px;
-line-height: 160%;
+const StatusText = styled.div<{ lessPadding?: boolean }>`
+  padding: ${(props) => (props.lessPadding ? "10px" : "18px")} 40px;
+  font-family: "Work Sans", sans-serif;
+  font-size: 14px;
+  line-height: 160%;
+  color: #aaaabb;
+  text-align: center;
 `;
 `;
 
 
 const FormWrapper = styled.div`
 const FormWrapper = styled.div`

+ 5 - 9
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -289,18 +289,14 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
       case "metrics":
       case "metrics":
         return <MetricsSection currentChart={chart} />;
         return <MetricsSection currentChart={chart} />;
       case "status":
       case "status":
-        let activeJobs = Object.values(this.state.controllers)[0]?.status.active;
+        let activeJobs = Object.values(this.state.controllers)[0]?.status
+          .active;
         let selectors = activeJobs?.map((job: any) => {
         let selectors = activeJobs?.map((job: any) => {
-          return `job-name=${job.name},controller-uid=${job.uid}`
-        })
+          return `job-name=${job.name},controller-uid=${job.uid}`;
+        });
 
 
         if (chart.chart.metadata.name == "job") {
         if (chart.chart.metadata.name == "job") {
-          return (
-            <StatusSection
-              currentChart={chart}
-              selectors={selectors}
-            />
-          );
+          return <StatusSection currentChart={chart} selectors={selectors} />;
         }
         }
         return <StatusSection currentChart={chart} />;
         return <StatusSection currentChart={chart} />;
       case "settings":
       case "settings":

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

@@ -111,8 +111,8 @@ export default class ControllerTab extends Component<PropsType, StateType> {
           c.status?.desiredNumberScheduled || 0,
           c.status?.desiredNumberScheduled || 0,
         ];
         ];
       case "job":
       case "job":
-        console.log(c)
-        return [1, 1]
+        console.log(c);
+        return [1, 1];
     }
     }
   };
   };
 
 
@@ -156,7 +156,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
     let status = available == total ? "running" : "waiting";
     let status = available == total ? "running" : "waiting";
 
 
     if (controller.kind.toLowerCase() === "job" && this.state.raw.length == 0) {
     if (controller.kind.toLowerCase() === "job" && this.state.raw.length == 0) {
-      status = "completed" 
+      status = "completed";
     }
     }
 
 
     return (
     return (

+ 6 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx

@@ -120,9 +120,12 @@ export default class StatusSection extends Component<PropsType, StateType> {
           revision: currentChart.version,
           revision: currentChart.version,
         }
         }
       )
       )
-      .then((res : any) => {
-        let controllers = currentChart.chart.metadata.name == "job" ? res.data[0]?.status.active : res.data
-        this.setState({controllers, loading: false})
+      .then((res: any) => {
+        let controllers =
+          currentChart.chart.metadata.name == "job"
+            ? res.data[0]?.status.active
+            : res.data;
+        this.setState({ controllers, loading: false });
       })
       })
       .catch((err) => {
       .catch((err) => {
         setCurrentError(JSON.stringify(err));
         setCurrentError(JSON.stringify(err));

+ 1 - 12
dashboard/src/main/home/navbar/Navbar.tsx

@@ -20,17 +20,6 @@ export default class Navbar extends Component<PropsType, StateType> {
     showDropdown: false,
     showDropdown: false,
   };
   };
 
 
-  handleLogout = (): void => {
-    let { logOut } = this.props;
-    let { setCurrentError } = this.context;
-
-    // Attempt user logout
-    api
-      .logOutUser("<token>", {}, {})
-      .then(logOut)
-      .catch((err) => setCurrentError(err.response.data.errors[0]));
-  };
-
   renderSettingsDropdown = () => {
   renderSettingsDropdown = () => {
     if (this.state.showDropdown) {
     if (this.state.showDropdown) {
       return (
       return (
@@ -42,7 +31,7 @@ export default class Navbar extends Component<PropsType, StateType> {
             <DropdownLabel>
             <DropdownLabel>
               {this.context.user && this.context.user.email}
               {this.context.user && this.context.user.email}
             </DropdownLabel>
             </DropdownLabel>
-            <LogOutButton onClick={this.handleLogout}>
+            <LogOutButton onClick={this.props.logOut}>
               <i className="material-icons">keyboard_return</i> Log Out
               <i className="material-icons">keyboard_return</i> Log Out
             </LogOutButton>
             </LogOutButton>
           </Dropdown>
           </Dropdown>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 15 - 15
dashboard/webpack.config.js

@@ -18,16 +18,16 @@ module.exports = () => {
       rules: [
       rules: [
         {
         {
           test: /\.(ts|tsx)$/,
           test: /\.(ts|tsx)$/,
-          loader: "ts-loader"
+          loader: "ts-loader",
         },
         },
         {
         {
           enforce: "pre",
           enforce: "pre",
           test: /\.js$/,
           test: /\.js$/,
-          loader: "source-map-loader"
+          loader: "source-map-loader",
         },
         },
         {
         {
           test: /\.(png|svg|jpg|gif|mp3)$/,
           test: /\.(png|svg|jpg|gif|mp3)$/,
-          use: ["file-loader"]
+          use: ["file-loader"],
         },
         },
         { test: /\.css$/, use: ["css-loader"] },
         { test: /\.css$/, use: ["css-loader"] },
         {
         {
@@ -37,31 +37,31 @@ module.exports = () => {
               loader: "file-loader",
               loader: "file-loader",
               options: {
               options: {
                 name: "[name].[ext]",
                 name: "[name].[ext]",
-                outputPath: "fonts/"
-              }
-            }
-          ]
-        }
-      ]
+                outputPath: "fonts/",
+              },
+            },
+          ],
+        },
+      ],
     },
     },
     resolve: {
     resolve: {
       modules: [path.resolve(__dirname, "src"), "node_modules"],
       modules: [path.resolve(__dirname, "src"), "node_modules"],
-      extensions: ["*", ".tsx", ".ts", ".js", ".jsx", ".json"]
+      extensions: ["*", ".tsx", ".ts", ".js", ".jsx", ".json"],
     },
     },
     output: {
     output: {
       filename: "bundle.js",
       filename: "bundle.js",
       path: path.resolve(__dirname, "build"),
       path: path.resolve(__dirname, "build"),
-      publicPath: "/"
+      publicPath: "/",
     },
     },
     devServer: {
     devServer: {
-      historyApiFallback: true
+      historyApiFallback: true,
     },
     },
     plugins: [
     plugins: [
       new HtmlWebpackPlugin({
       new HtmlWebpackPlugin({
         template: path.resolve(__dirname, "src", "index.html"),
         template: path.resolve(__dirname, "src", "index.html"),
-        segmentKey: `${process.env.SEGMENT_PUBLIC_KEY}`
+        segmentKey: `${process.env.SEGMENT_PUBLIC_KEY}`,
       }),
       }),
-      new webpack.DefinePlugin(envKeys)
-    ]
+      new webpack.DefinePlugin(envKeys),
+    ],
   };
   };
 };
 };