Quellcode durchsuchen

consolidated headers, hoisted all resource modals into full view, updated all chart tabs to work outside modals

jusrhee vor 4 Jahren
Ursprung
Commit
37fd1363eb
33 geänderte Dateien mit 512 neuen und 1845 gelöschten Zeilen
  1. BIN
      dashboard/src/assets/back_arrow.png
  2. 2 5
      dashboard/src/components/ResourceTab.tsx
  3. 1 0
      dashboard/src/components/TabRegion.tsx
  4. 102 0
      dashboard/src/components/TitleSection.tsx
  5. 3 3
      dashboard/src/components/YamlEditor.tsx
  6. 1 1
      dashboard/src/components/values-form/FormWrapper.tsx
  7. 3 4
      dashboard/src/main/home/Home.tsx
  8. 11 59
      dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx
  9. 10 39
      dashboard/src/main/home/cluster-dashboard/DashboardHeader.tsx
  10. 4 36
      dashboard/src/main/home/cluster-dashboard/dashboard/Dashboard.tsx
  11. 1 1
      dashboard/src/main/home/cluster-dashboard/dashboard/node-view/ExpandedNodeView.tsx
  12. 2 2
      dashboard/src/main/home/cluster-dashboard/env-groups/CreateEnvGroup.tsx
  13. 60 122
      dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroup.tsx
  14. 85 126
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx
  15. 57 104
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx
  16. 16 3
      dashboard/src/main/home/cluster-dashboard/expanded-chart/GraphSection.tsx
  17. 19 5
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ListSection.tsx
  18. 1 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx
  19. 19 3
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ValuesYaml.tsx
  20. 2 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/GraphDisplay.tsx
  21. 18 6
      dashboard/src/main/home/cluster-dashboard/expanded-chart/metrics/MetricsSection.tsx
  22. 2 2
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx
  23. 18 5
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx
  24. 2 2
      dashboard/src/main/home/dashboard/ClusterList.tsx
  25. 9 35
      dashboard/src/main/home/dashboard/Dashboard.tsx
  26. 29 61
      dashboard/src/main/home/integrations/IntegrationCategories.tsx
  27. 19 40
      dashboard/src/main/home/integrations/Integrations.tsx
  28. 3 34
      dashboard/src/main/home/launch/Launch.tsx
  29. 0 1040
      dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx
  30. 2 2
      dashboard/src/main/home/launch/expanded-template/TemplateInfo.tsx
  31. 4 54
      dashboard/src/main/home/launch/launch-flow/LaunchFlow.tsx
  32. 4 28
      dashboard/src/main/home/new-project/NewProject.tsx
  33. 3 23
      dashboard/src/main/home/project-settings/ProjectSettings.tsx

BIN
dashboard/src/assets/back_arrow.png


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

@@ -142,15 +142,12 @@ export default class ResourceTab extends Component<PropsType, StateType> {
 const StyledResourceTab = styled.div`
   width: 100%;
   margin-bottom: 2px;
+  overflow: hidden;
   background: #ffffff11;
   border-bottom-left-radius: ${(props: {
     isLast: boolean;
     roundAllCorners: boolean;
-  }) => (props.isLast ? "5px" : "")};
-  border-bottom-right-radius: ${(props: {
-    isLast: boolean;
-    roundAllCorners: boolean;
-  }) => (props.roundAllCorners && props.isLast ? "5px" : "")};
+  }) => (props.isLast ? "10px" : "")};
 `;
 
 const Tooltip = styled.div`

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

@@ -91,4 +91,5 @@ const StyledTabRegion = styled.div`
   height: 100%;
   position: relative;
   overflow-y: auto;
+  overflow: visible;
 `;

+ 102 - 0
dashboard/src/components/TitleSection.tsx

@@ -0,0 +1,102 @@
+import React from "react";
+import styled from "styled-components";
+
+interface Props {
+  children: React.ReactNode;
+  icon?: any;
+  iconWidth?: string;
+  capitalize?: boolean;
+  handleNavBack?: () => void;
+}
+
+const TitleSection: React.FC<Props> = ({ 
+  children, 
+  icon,
+  iconWidth,
+  capitalize,
+  handleNavBack,
+}) => {
+  return (
+    <StyledTitleSection>
+      {
+        handleNavBack && (
+          <BackButton>
+            <i className="material-icons" onClick={handleNavBack}>
+              keyboard_backspace
+            </i>
+          </BackButton>
+        )
+      }
+      {
+        icon && (
+          <Icon width={iconWidth} src={icon}/>
+        )
+      }
+      <StyledTitle capitalize={capitalize}>{children}</StyledTitle>
+    </StyledTitleSection>
+  );
+};
+
+export default TitleSection;
+
+const BackButton = styled.div`
+  > i {
+    cursor: pointer;
+    font-size 24px;
+    color: #969Fbbaa;
+    margin-right: 10px;
+    padding: 3px;
+    margin-left: 0px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+  }
+`;
+
+const StyledTitleSection = styled.div`
+  margin-bottom: 15px;
+  display: flex;
+  align-items: center;
+`;
+
+const Icon = styled.img<{ width: string }>`
+  width: ${props => props.width || "28px"};
+  margin-right: 16px;
+`;
+
+const StyledTitle = styled.div<{ capitalize: boolean }>`
+  font-size: 24px;
+  font-weight: 600;
+  user-select: text;
+  text-transform: ${props => props.capitalize ? "capitalize" : ""};
+  display: flex;
+  align-items: center;
+
+  > i {
+    margin-left: 10px;
+    cursor: pointer;
+    font-size: 18px;
+    color: #858faaaa;
+    padding: 5px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+    margin-bottom: -3px;
+  }
+  
+  > a {
+    > i {
+      display: flex;
+      align-items: center;
+      margin-bottom: -2px;
+      font-size: 18px;
+      margin-left: 15px;
+      color: #858faaaa;
+      :hover {
+        color: #aaaabb;
+      }
+    }
+  }
+`;

+ 3 - 3
dashboard/src/components/YamlEditor.tsx

@@ -52,7 +52,7 @@ class YamlEditor extends Component<PropsType, StateType> {
             editorProps={{ $blockScrolling: true }}
             height={this.props.height}
             width="100%"
-            style={{ borderRadius: "5px" }}
+            style={{ borderRadius: "10px" }}
             showPrintMargin={false}
             showGutter={true}
             highlightActiveLine={true}
@@ -67,9 +67,9 @@ class YamlEditor extends Component<PropsType, StateType> {
 export default YamlEditor;
 
 const Editor = styled.form`
-  border-radius: ${(props: { border: boolean }) => (props.border ? "5px" : "")};
+  border-radius: ${(props: { border: boolean }) => (props.border ? "10px" : "")};
   border: ${(props: { border: boolean }) =>
-    props.border ? "1px solid #ffffff22" : ""};
+    props.border ? "1px solid #ffffff33" : ""};
 `;
 
 const Holder = styled.div`

+ 1 - 1
dashboard/src/components/values-form/FormWrapper.tsx

@@ -445,7 +445,7 @@ export default class FormWrapper extends Component<PropsType, StateType> {
     let showSave = this.showSaveButton();
     return (
       <>
-        {this.props.isInModal ? (
+        {this.props.isInModal || !showSave ? (
           <StyledValuesWrapper showSave={showSave}>
             {this.renderContents(showSave)}
           </StyledValuesWrapper>

+ 3 - 4
dashboard/src/main/home/Home.tsx

@@ -605,20 +605,19 @@ export default withRouter(withAuth(Home));
 const ViewWrapper = styled.div`
   height: 100%;
   width: 100vw;
-  padding-top: 30px;
+  padding-top: 90px;
   overflow-y: auto;
   display: flex;
   flex: 1;
   justify-content: center;
   background: #202227;
   position: relative;
+  
 `;
 
 const DashboardWrapper = styled.div`
-  width: 80%;
-  padding-top: 50px;
+  width: 83%;
   min-width: 300px;
-  padding-bottom: 120px;
 `;
 
 const StyledHome = styled.div`

+ 11 - 59
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -13,6 +13,7 @@ import {
   pushQueryParams,
 } from "shared/routing";
 
+import DashboardHeader from "./DashboardHeader";
 import ChartList from "./chart/ChartList";
 import EnvGroupDashboard from "./env-groups/EnvGroupDashboard";
 import NamespaceSelector from "./NamespaceSelector";
@@ -112,14 +113,6 @@ class ClusterDashboard extends Component<PropsType, StateType> {
     }
   }
 
-  renderDashboardIcon = () => {
-    if (this.props.currentView === "jobs") {
-      return <Img src={monojob} />;
-    } else {
-      return <Img src={monoweb} />;
-    }
-  };
-
   getDescription = (currentView: string): string => {
     if (currentView === "jobs") {
       return "Scripts and tasks that run once or on a repeating interval.";
@@ -183,22 +176,11 @@ class ClusterDashboard extends Component<PropsType, StateType> {
 
     return (
       <>
-        <TitleSection>
-          {this.renderDashboardIcon()}
-          <Title>{currentView}</Title>
-        </TitleSection>
-
-        <InfoSection>
-          <TopRow>
-            <InfoLabel>
-              <i className="material-icons">info</i> Info
-            </InfoLabel>
-          </TopRow>
-          <Description>{this.getDescription(currentView)}</Description>
-        </InfoSection>
-
-        <LineBreak />
-
+        <DashboardHeader
+          image={currentView === "jobs" ? monojob : monoweb}
+          title={currentView}
+          description={this.getDescription(currentView)}
+        />
         {this.renderBody()}
       </>
     );
@@ -250,6 +232,11 @@ ClusterDashboard.contextType = Context;
 
 export default withRouter(withAuth(ClusterDashboard));
 
+const Br = styled.div`
+  width: 100%;
+  height: 1px;
+`;
+
 const ControlRow = styled.div`
   display: flex;
   justify-content: ${(props: { hasMultipleChilds: boolean }) => {
@@ -401,41 +388,6 @@ const Img = styled.img`
   width: 30px;
 `;
 
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 18px;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  text-transform: capitalize;
-`;
-
-const TitleSection = styled.div`
-  height: 80px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding-left: 0px;
-
-  > i {
-    margin-left: 10px;
-    cursor: pointer;
-    font-size: 18px;
-    color: #858faaaa;
-    padding: 5px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-    margin-bottom: -3px;
-  }
-`;
-
 const SortFilterWrapper = styled.div`
   width: 468px;
   display: flex;

+ 10 - 39
dashboard/src/main/home/cluster-dashboard/DashboardHeader.tsx

@@ -3,6 +3,8 @@ import styled from "styled-components";
 
 import { Context } from "shared/Context";
 
+import TitleSection from "components/TitleSection";
+
 type PropsType = {
   image: any;
   title: string;
@@ -15,11 +17,12 @@ export default class DashboardHeader extends Component<PropsType, StateType> {
   render() {
     return (
       <>
-        <TitleSection>
-          <Img src={this.props.image} />
-          <Title>{this.props.title}</Title>
+        <TitleSection capitalize={true} icon={this.props.image}>
+          {this.props.title}
         </TitleSection>
 
+        <Br />
+
         <InfoSection>
           <TopRow>
             <InfoLabel>
@@ -37,8 +40,9 @@ export default class DashboardHeader extends Component<PropsType, StateType> {
 
 DashboardHeader.contextType = Context;
 
-const Img = styled.img`
-  width: 30px;
+const Br = styled.div`
+  width: 100%;
+  height: 1px;
 `;
 
 const LineBreak = styled.div`
@@ -82,16 +86,6 @@ const InfoSection = styled.div`
   margin-bottom: 35px;
 `;
 
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 18px;
-  color: #ffffff;
-  text-transform: capitalize;
-  white-space: nowrap;
-`;
-
 const ClusterLabel = styled.div`
   color: #ffffff22;
   font-size: 14px;
@@ -100,27 +94,4 @@ const ClusterLabel = styled.div`
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  height: 80px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding-left: 0px;
-
-  > i {
-    margin-left: 10px;
-    cursor: pointer;
-    font-size 18px;
-    color: #858FAAaa;
-    padding: 5px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-    margin-bottom: -3px;
-  }
-`;
+`;

+ 4 - 36
dashboard/src/main/home/cluster-dashboard/dashboard/Dashboard.tsx

@@ -3,6 +3,7 @@ import styled from "styled-components";
 
 import { Context } from "shared/Context";
 import TabSelector from "components/TabSelector";
+import TitleSection from "components/TitleSection";
 
 import NodeList from "./NodeList";
 
@@ -56,7 +57,7 @@ export const Dashboard: React.FunctionComponent = () => {
         <DashboardIcon>
           <i className="material-icons">device_hub</i>
         </DashboardIcon>
-        <Title>{context.currentCluster.name}</Title>
+        {context.currentCluster.name}
       </TitleSection>
 
       <InfoSection>
@@ -86,6 +87,7 @@ const DashboardIcon = styled.div`
   min-width: 45px;
   width: 45px;
   border-radius: 5px;
+  margin-right: 17px;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -127,38 +129,4 @@ const InfoSection = styled.div`
   font-family: "Work Sans", sans-serif;
   margin-left: 0px;
   margin-bottom: 35px;
-`;
-
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 18px;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  height: 80px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding-left: 0px;
-
-  > i {
-    margin-left: 10px;
-    cursor: pointer;
-    font-size: 18px;
-    color: #858faaaa;
-    padding: 5px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-    margin-bottom: -3px;
-  }
-`;
+`;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/node-view/ExpandedNodeView.tsx

@@ -185,7 +185,7 @@ const IconWrapper = styled.div`
 `;
 
 const Title = styled.div`
-  font-size: 18px;
+  font-size: 20px;
   font-weight: 500;
   display: flex;
   align-items: center;

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/env-groups/CreateEnvGroup.tsx

@@ -325,8 +325,8 @@ const Subtitle = styled.div`
 `;
 
 const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
+  font-size: 20px;
+  font-weight: 500;
   font-family: "Work Sans", sans-serif;
   margin-left: 15px;
   border-radius: 2px;

+ 60 - 122
dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroup.tsx

@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 import styled from "styled-components";
 import close from "assets/close.png";
+import backArrow from "assets/back_arrow.png";
 import key from "assets/key.svg";
 import _ from "lodash";
 
@@ -9,6 +10,7 @@ import { Context } from "shared/Context";
 import { isAlphanumeric } from "shared/common";
 import api from "shared/api";
 
+import TitleSection from "components/TitleSection";
 import SaveButton from "components/SaveButton";
 import ConfirmOverlay from "components/ConfirmOverlay";
 import Loading from "components/Loading";
@@ -373,8 +375,11 @@ class ExpandedEnvGroup extends Component<PropsType, StateType> {
 
     return (
       <>
-        <CloseOverlay onClick={closeExpanded} />
         <StyledExpandedChart>
+          <BackButton onClick={closeExpanded}>
+            <BackButtonImg src={backArrow} />
+          </BackButton>
+
           <ConfirmOverlay
             show={this.state.showDeleteOverlay}
             message={`Are you sure you want to delete ${name}?`}
@@ -383,29 +388,21 @@ class ExpandedEnvGroup extends Component<PropsType, StateType> {
           />
           {this.renderDeleteOverlay()}
 
-          <HeaderWrapper>
-            <TitleSection>
-              <Title>
-                <IconWrapper>
-                  <Icon src={key} />
-                </IconWrapper>
-                {name}
-              </Title>
-              <InfoWrapper>
-                <LastDeployed>
-                  Last updated {this.readableDate(timestamp)}
-                </LastDeployed>
-              </InfoWrapper>
-
-              <TagWrapper>
-                Namespace <NamespaceTag>{namespace}</NamespaceTag>
-              </TagWrapper>
-            </TitleSection>
-
-            <CloseButton onClick={closeExpanded}>
-              <CloseButtonImg src={close} />
-            </CloseButton>
-          </HeaderWrapper>
+          <TitleSection 
+            icon={key}
+            iconWidth="33px"
+          >
+            {name}
+            <TagWrapper>
+              Namespace <NamespaceTag>{namespace}</NamespaceTag>
+            </TagWrapper>
+          </TitleSection>
+
+          <InfoWrapper>
+            <LastDeployed>
+              Last updated {this.readableDate(timestamp)}
+            </LastDeployed>
+          </InfoWrapper>
 
           <TabRegion
             currentTab={this.state.currentTab}
@@ -425,6 +422,33 @@ ExpandedEnvGroup.contextType = Context;
 
 export default withAuth(ExpandedEnvGroup);
 
+const BackButton = styled.div`
+  position: absolute;
+  top: 0px;
+  right: 0px;;
+  display: flex;
+  width: 36px;
+  cursor: pointer;
+  height: 36px;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+    > img {
+      opacity: 1.0;
+    }
+  }
+`;
+
+const BackButtonImg = styled.img`
+  width: 16px;
+  opacity: 0.75;
+`;
+
 const Button = styled.button`
   height: 35px;
   font-size: 13px;
@@ -464,6 +488,7 @@ const InnerWrapper = styled.div<{ full?: boolean }>`
 const TabWrapper = styled.div`
   height: 100%;
   width: 100%;
+  padding-bottom: 65px;
   overflow: hidden;
 `;
 
@@ -499,37 +524,10 @@ const DeleteOverlay = styled.div`
   }
 `;
 
-const CloseOverlay = styled.div`
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background: #202227;
-  animation: fadeIn 0.2s 0s;
-  opacity: 0;
-  animation-fill-mode: forwards;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
-const HeaderWrapper = styled.div``;
-
-const Dot = styled.div`
-  margin-right: 9px;
-  margin-left: 9px;
-`;
-
 const InfoWrapper = styled.div`
   display: flex;
   align-items: center;
-  margin: 24px 0px 17px 0px;
+  margin: 10px 0px 17px 0px;
   height: 20px;
 `;
 
@@ -543,13 +541,13 @@ const LastDeployed = styled.div`
 `;
 
 const TagWrapper = styled.div`
-  position: absolute;
-  right: 0px;
-  bottom: 0px;
   height: 20px;
   font-size: 12px;
   display: flex;
+  margin-left: 20px;
+  margin-bottom: -3px;
   align-items: center;
+  font-weight: 400;
   justify-content: center;
   color: #ffffff44;
   border: 1px solid #ffffff44;
@@ -574,85 +572,25 @@ const NamespaceTag = styled.div`
   border-bottom-left-radius: 0px;
 `;
 
-const Icon = styled.img`
-  width: 100%;
-`;
-
-const IconWrapper = styled.div`
-  color: #efefef;
-  font-size: 16px;
-  height: 20px;
-  width: 20px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  border-radius: 3px;
-  margin-right: 12px;
-
-  > i {
-    font-size: 20px;
-  }
-`;
-
-const Title = styled.div`
-  font-size: 18px;
-  font-weight: 500;
-  display: flex;
-  align-items: center;
-`;
-
-const TitleSection = styled.div`
-  width: 100%;
-  position: relative;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
 const StyledExpandedChart = styled.div`
-  width: calc(100% - 50px);
-  height: calc(100% - 50px);
+  width: 100%;
   z-index: 0;
-  position: absolute;
-  top: 25px;
-  left: 25px;
-  overflow: hidden;
-  border-radius: 10px;
-  background: #26272f;
-  box-shadow: 0 5px 12px 4px #00000033;
-  animation: floatIn 0.3s;
+  position: relative;
+  animation: fadeIn 0.3s;
   animation-timing-function: ease-out;
   animation-fill-mode: forwards;
-  padding: 25px;
   display: flex;
+  overflow-y: auto;
+  padding-bottom: 120px;
   flex-direction: column;
+  overflow: visible;
 
-  @keyframes floatIn {
+  @keyframes fadeIn {
     from {
       opacity: 0;
-      transform: translateY(30px);
     }
     to {
       opacity: 1;
-      transform: translateY(0px);
     }
   }
 `;

+ 85 - 126
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -8,7 +8,7 @@ import React, {
 } from "react";
 import styled from "styled-components";
 import yaml from "js-yaml";
-import close from "assets/close.png";
+import backArrow from "assets/back_arrow.png";
 import _ from "lodash";
 import loadingSrc from "assets/loading.gif";
 
@@ -34,6 +34,7 @@ import StatusSection from "./status/StatusSection";
 import SettingsSection from "./SettingsSection";
 import { useWebsockets } from "shared/hooks/useWebsockets";
 import useAuth from "shared/auth/useAuth";
+import TitleSection from "components/TitleSection";
 
 type Props = {
   namespace: string;
@@ -646,8 +647,11 @@ const ExpandedChart: React.FC<Props> = (props) => {
 
   return (
     <>
-      <CloseOverlay onClick={props.closeChart} />
       <StyledExpandedChart>
+        <BackButton onClick={props.closeChart}>
+          <BackButtonImg src={backArrow} />
+        </BackButton>
+        
         <ConfirmOverlay
           show={showDeleteOverlay}
           message={`Are you sure you want to delete ${currentChart.name}?`}
@@ -660,34 +664,32 @@ const ExpandedChart: React.FC<Props> = (props) => {
           </DeleteOverlay>
         )}
         <HeaderWrapper>
-          <TitleSection>
-            <Title>
-              <IconWrapper>{renderIcon()}</IconWrapper>
-              {currentChart.name}
-            </Title>
-            {currentChart.chart.metadata.name != "worker" &&
-              currentChart.chart.metadata.name != "job" &&
-              renderUrl()}
-            <InfoWrapper>
-              <StatusIndicator
-                controllers={controllers}
-                status={currentChart.info.status}
-                margin_left={"0px"}
-              />
-              <LastDeployed>
-                <Dot>•</Dot>Last deployed
-                {" " + getReadableDate(currentChart.info.last_deployed)}
-              </LastDeployed>
-            </InfoWrapper>
-
+          <TitleSection 
+            icon={currentChart.chart.metadata.icon}
+            iconWidth="33px"
+          >
+            {currentChart.name}
             <TagWrapper>
               Namespace <NamespaceTag>{currentChart.namespace}</NamespaceTag>
             </TagWrapper>
           </TitleSection>
 
-          <CloseButton onClick={props.closeChart}>
-            <CloseButtonImg src={close} />
-          </CloseButton>
+          {
+            currentChart.chart.metadata.name != "worker" &&
+            currentChart.chart.metadata.name != "job" &&
+            renderUrl()
+          }
+          <InfoWrapper>
+            <StatusIndicator
+              controllers={controllers}
+              status={currentChart.info.status}
+              margin_left={"0px"}
+            />
+            <LastDeployed>
+              <Dot>•</Dot>Last deployed
+              {" " + getReadableDate(currentChart.info.last_deployed)}
+            </LastDeployed>
+          </InfoWrapper>
 
           <RevisionSection
             showRevisions={showRevisions}
@@ -709,30 +711,27 @@ const ExpandedChart: React.FC<Props> = (props) => {
             upgradeVersion={handleUpgradeVersion}
           />
         </HeaderWrapper>
-        <BodyWrapper>
-          <FormWrapper
-            isReadOnly={
-              imageIsPlaceholder ||
-              !isAuthorized("application", "", ["get", "update"])
-            }
-            formData={currentChart.form}
-            tabOptions={tabOptions}
-            isInModal={true}
-            renderTabContents={renderTabContents}
-            onSubmit={onSubmit}
-            saveValuesStatus={saveValuesStatus}
-            externalValues={{
-              namespace: props.namespace,
-              clusterId: currentCluster.id,
-            }}
-            color={isPreview ? "#f5cb42" : null}
-            addendum={
-              <TabButton onClick={toggleDevOpsMode} devOpsMode={devOpsMode}>
-                <i className="material-icons">offline_bolt</i> DevOps Mode
-              </TabButton>
-            }
-          />
-        </BodyWrapper>
+        <FormWrapper
+          isReadOnly={
+            imageIsPlaceholder ||
+            !isAuthorized("application", "", ["get", "update"])
+          }
+          formData={currentChart.form}
+          tabOptions={tabOptions}
+          renderTabContents={renderTabContents}
+          onSubmit={onSubmit}
+          saveValuesStatus={saveValuesStatus}
+          externalValues={{
+            namespace: props.namespace,
+            clusterId: currentCluster.id,
+          }}
+          color={isPreview ? "#f5cb42" : null}
+          addendum={
+            <TabButton onClick={toggleDevOpsMode} devOpsMode={devOpsMode}>
+              <i className="material-icons">offline_bolt</i> DevOps Mode
+            </TabButton>
+          }
+        />
       </StyledExpandedChart>
     </>
   );
@@ -742,6 +741,33 @@ export default ExpandedChart;
 
 const TextWrap = styled.div``;
 
+const BackButton = styled.div`
+  position: absolute;
+  top: 0px;
+  right: 0px;;
+  display: flex;
+  width: 36px;
+  cursor: pointer;
+  height: 36px;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+    > img {
+      opacity: 1.0;
+    }
+  }
+`;
+
+const BackButtonImg = styled.img`
+  width: 16px;
+  opacity: 0.75;
+`;
+
 const Header = styled.div`
   font-weight: 500;
   color: #aaaabb;
@@ -768,12 +794,6 @@ const Spinner = styled.img`
   margin-bottom: -2px;
 `;
 
-const BodyWrapper = styled.div`
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-`;
-
 const DeleteOverlay = styled.div`
   position: absolute;
   top: 0px;
@@ -858,26 +878,6 @@ const TabButton = styled.div`
   }
 `;
 
-const CloseOverlay = styled.div`
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background: #202227;
-  animation: fadeIn 0.2s 0s;
-  opacity: 0;
-  animation-fill-mode: forwards;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
 const HeaderWrapper = styled.div``;
 
 const Dot = styled.div`
@@ -901,13 +901,13 @@ const LastDeployed = styled.div`
 `;
 
 const TagWrapper = styled.div`
-  position: absolute;
-  bottom: 0px;
-  right: 0px;
   height: 20px;
   font-size: 12px;
   display: flex;
+  margin-left: 20px;
+  margin-bottom: -3px;
   align-items: center;
+  font-weight: 400;
   justify-content: center;
   color: #ffffff44;
   border: 1px solid #ffffff44;
@@ -952,66 +952,25 @@ const IconWrapper = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 18px;
-  font-weight: 500;
-  display: flex;
-  align-items: center;
-  user-select: text;
-`;
-
-const TitleSection = styled.div`
-  width: 100%;
-  position: relative;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
 const StyledExpandedChart = styled.div`
-  width: calc(100% - 50px);
-  height: calc(100% - 50px);
+  width: 100%;
   z-index: 0;
-  position: absolute;
-  top: 25px;
-  left: 25px;
-  border-radius: 10px;
-  background: #26272f;
-  box-shadow: 0 5px 12px 4px #00000033;
-  animation: floatIn 0.3s;
+  position: relative;
+  animation: fadeIn 0.3s;
   animation-timing-function: ease-out;
   animation-fill-mode: forwards;
-  padding: 25px;
   display: flex;
-  overflow: hidden;
+  overflow-y: auto;
+  padding-bottom: 120px;
   flex-direction: column;
+  overflow: visible;
 
-  @keyframes floatIn {
+  @keyframes fadeIn {
     from {
       opacity: 0;
-      transform: translateY(30px);
     }
     to {
       opacity: 1;
-      transform: translateY(0px);
     }
   }
 `;

+ 57 - 104
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx

@@ -1,7 +1,8 @@
 import React, { Component } from "react";
 import styled from "styled-components";
 import yaml from "js-yaml";
-import close from "assets/close.png";
+
+import backArrow from "assets/back_arrow.png";
 import _ from "lodash";
 import loading from "assets/loading.gif";
 
@@ -12,11 +13,10 @@ import api from "shared/api";
 import SaveButton from "components/SaveButton";
 import ConfirmOverlay from "components/ConfirmOverlay";
 import Loading from "components/Loading";
-import TabRegion from "components/TabRegion";
+import TitleSection from "components/TitleSection";
 import JobList from "./jobs/JobList";
 import SettingsSection from "./SettingsSection";
 import FormWrapper from "components/values-form/FormWrapper";
-import { PlaceHolder } from "brace";
 import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
 
 type PropsType = WithAuthProps & {
@@ -509,19 +509,6 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
     this.setState({ tabOptions });
   }
 
-  renderIcon = () => {
-    let { currentChart } = this.state;
-
-    if (
-      currentChart.chart.metadata.icon &&
-      currentChart.chart.metadata.icon !== ""
-    ) {
-      return <Icon src={currentChart.chart.metadata.icon} />;
-    } else {
-      return <i className="material-icons">tonality</i>;
-    }
-  };
-
   readableDate = (s: string) => {
     let ts = new Date(s);
     let date = ts.toLocaleDateString();
@@ -585,8 +572,11 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
 
     return (
       <>
-        <CloseOverlay onClick={closeChart} />
         <StyledExpandedChart>
+          <BackButton onClick={closeChart}>
+            <BackButtonImg src={backArrow} />
+          </BackButton>
+
           <ConfirmOverlay
             show={this.state.showDeleteOverlay}
             message={`Are you sure you want to delete ${currentChart.name}?`}
@@ -596,27 +586,23 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
           {this.renderDeleteOverlay()}
 
           <HeaderWrapper>
-            <TitleSection>
-              <Title>
-                <IconWrapper>{this.renderIcon()}</IconWrapper>
-                {chart.name}
-              </Title>
-              <InfoWrapper>
-                <LastDeployed>
-                  Run {this.state.jobs.length} times <Dot>•</Dot>Last template
-                  update at
-                  {" " + this.readableDate(chart.info.last_deployed)}
-                </LastDeployed>
-              </InfoWrapper>
-
+            <TitleSection
+              icon={currentChart.chart.metadata.icon}
+              iconWidth="33px"
+            >
+              {chart.name}
               <TagWrapper>
                 Namespace <NamespaceTag>{chart.namespace}</NamespaceTag>
               </TagWrapper>
             </TitleSection>
 
-            <CloseButton onClick={closeChart}>
-              <CloseButtonImg src={close} />
-            </CloseButton>
+            <InfoWrapper>
+              <LastDeployed>
+                Run {this.state.jobs.length} times <Dot>•</Dot>Last template
+                update at
+                {" " + this.readableDate(chart.info.last_deployed)}
+              </LastDeployed>
+            </InfoWrapper>
           </HeaderWrapper>
 
           <BodyWrapper>
@@ -631,7 +617,6 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
               }
               formData={this.state.formData}
               tabOptions={this.state.tabOptions}
-              isInModal={true}
               renderTabContents={this.renderTabContents}
               tabOptionsOnly={true}
               onSubmit={(formValues) =>
@@ -651,6 +636,33 @@ ExpandedJobChart.contextType = Context;
 
 export default withAuth(ExpandedJobChart);
 
+const BackButton = styled.div`
+  position: absolute;
+  top: 0px;
+  right: 0px;;
+  display: flex;
+  width: 36px;
+  cursor: pointer;
+  height: 36px;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+    > img {
+      opacity: 1.0;
+    }
+  }
+`;
+
+const BackButtonImg = styled.img`
+  width: 16px;
+  opacity: 0.75;
+`;
+
 const TextWrap = styled.div``;
 
 const Header = styled.div`
@@ -688,6 +700,7 @@ const BodyWrapper = styled.div`
 const TabWrapper = styled.div`
   height: 100%;
   width: 100%;
+  padding-bottom: 47px;
   overflow: hidden;
 `;
 
@@ -723,26 +736,6 @@ const DeleteOverlay = styled.div`
   }
 `;
 
-const CloseOverlay = styled.div`
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background: #202227;
-  animation: fadeIn 0.2s 0s;
-  opacity: 0;
-  animation-fill-mode: forwards;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
 const HeaderWrapper = styled.div``;
 
 const Dot = styled.div`
@@ -767,13 +760,13 @@ const LastDeployed = styled.div`
 `;
 
 const TagWrapper = styled.div`
-  position: absolute;
-  right: 0px;
-  bottom: 0px;
   height: 20px;
   font-size: 12px;
   display: flex;
+  margin-left: 20px;
+  margin-bottom: -3px;
   align-items: center;
+  font-weight: 400;
   justify-content: center;
   color: #ffffff44;
   border: 1px solid #ffffff44;
@@ -818,65 +811,25 @@ const IconWrapper = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 18px;
-  font-weight: 500;
-  display: flex;
-  align-items: center;
-`;
-
-const TitleSection = styled.div`
-  width: 100%;
-  position: relative;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
 const StyledExpandedChart = styled.div`
-  width: calc(100% - 50px);
-  height: calc(100% - 50px);
+  width: 100%;
   z-index: 0;
-  position: absolute;
-  top: 25px;
-  left: 25px;
-  border-radius: 10px;
-  background: #26272f;
-  box-shadow: 0 5px 12px 4px #00000033;
-  animation: floatIn 0.3s;
+  position: relative;
+  animation: fadeIn 0.3s;
   animation-timing-function: ease-out;
   animation-fill-mode: forwards;
-  padding: 25px;
   display: flex;
-  overflow: hidden;
+  overflow-y: auto;
+  padding-bottom: 120px;
   flex-direction: column;
+  overflow: visible;
 
-  @keyframes floatIn {
+  @keyframes fadeIn {
     from {
       opacity: 0;
-      transform: translateY(30px);
     }
     to {
       opacity: 1;
-      transform: translateY(0px);
     }
   }
 `;

+ 16 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/GraphSection.tsx

@@ -48,9 +48,22 @@ GraphSection.contextType = Context;
 
 const StyledGraphSection = styled.div`
   width: 100%;
-  height: 100%;
-  background: #ffffff11;
+  height: 450px;
   font-size: 13px;
-  border-radius: 5px;
   overflow: hidden;
+  border-radius: 10px;
+  border: 1px solid #ffffff33;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
 `;

+ 19 - 5
dashboard/src/main/home/cluster-dashboard/expanded-chart/ListSection.tsx

@@ -120,13 +120,13 @@ ListSection.contextType = Context;
 const YamlWrapper = styled.div`
   width: 100%;
   height: 100%;
+  overflow: visible;
 `;
 
 const TabWrapper = styled.div`
   min-width: 200px;
   width: 35%;
   margin-right: 10px;
-  border-radius: 5px;
   overflow: hidden;
   overflow-y: auto;
 `;
@@ -135,14 +135,28 @@ const FlexWrapper = styled.div`
   display: flex;
   flex: 1;
   height: 100%;
+  overflow: visible
 `;
 
 const StyledListSection = styled.div`
-  width: 100%;
-  height: 100%;
   display: flex;
-  position: relative;
   font-size: 13px;
-  border-radius: 5px;
+  width: 100%;
+  height: 450px;
+  font-size: 13px;
   overflow: hidden;
+  border-radius: 10px;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
 `;

+ 1 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -371,6 +371,7 @@ const A = styled.a`
 
 const Wrapper = styled.div`
   width: 100%;
+  padding-bottom: 65px;
   height: 100%;
 `;
 

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

@@ -111,13 +111,29 @@ ValuesYaml.contextType = Context;
 const Wrapper = styled.div`
   overflow: auto;
   height: calc(100% - 60px);
-  border-radius: 5px;
-  border: 1px solid #ffffff22;
+  border-radius: 10px;
+  border: 1px solid #ffffff33;
 `;
 
 const StyledValuesYaml = styled.div`
   display: flex;
   flex-direction: column;
   width: 100%;
-  height: 100%;
+  height: 450px;
+  font-size: 13px;
+  overflow: hidden;
+  border-radius: 10px;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
 `;

+ 2 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/GraphDisplay.tsx

@@ -634,11 +634,13 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
             </Checkbox>
             Show Type
           </ToggleLabel>
+          {/*
           <ExpandButton onClick={this.toggleExpanded}>
             <i className="material-icons">
               {this.state.isExpanded ? "close_fullscreen" : "open_in_full"}
             </i>
           </ExpandButton>
+          */}
         </ButtonSection>
         <InfoPanel
           setSuppressDisplay={(x: boolean) =>

+ 18 - 6
dashboard/src/main/home/cluster-dashboard/expanded-chart/metrics/MetricsSection.tsx

@@ -642,9 +642,7 @@ const DropdownAlt = styled(Dropdown)`
 `;
 
 const RangeWrapper = styled.div`
-  position: absolute;
-  top: 0;
-  right: 0;
+  float: right;
   font-weight: bold;
   width: 156px;
   margin-top: -8px;
@@ -681,11 +679,25 @@ const MetricsLabel = styled.div`
 
 const StyledMetricsSection = styled.div`
   width: 100%;
-  height: 100%;
+  height: 450px;
   display: flex;
   flex-direction: column;
   position: relative;
   font-size: 13px;
-  border-radius: 5px;
-  overflow: hidden;
+  border-radius: 10px;
+  border: 1px solid #ffffff33;
+  padding: 18px 22px;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
 `;

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx

@@ -382,7 +382,7 @@ const Refresh = styled.div`
 const LogTabs = styled.div`
   width: 100%;
   height: 25px;
-  background: #202227;
+  background: #121318;
   display: flex;
   flex-direction: row;
   align-items: center;
@@ -412,7 +412,7 @@ const LogStream = styled.div`
   flex: 1;
   float: right;
   height: 100%;
-  background: #202227;
+  background: #121318;
   user-select: text;
   max-width: 65%;
   overflow-y: auto;

+ 18 - 5
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx

@@ -150,14 +150,27 @@ const TabWrapper = styled.div`
 `;
 
 const StyledStatusSection = styled.div`
-  width: 100%;
-  height: 100%;
-  position: relative;
-  font-size: 13px;
   padding: 0px;
   user-select: text;
-  border-radius: 5px;
   overflow: hidden;
+  width: 100%;
+  height: 450px;
+  font-size: 13px;
+  overflow: hidden;
+  border-radius: 10px;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
 `;
 
 const Wrapper = styled.div`

+ 2 - 2
dashboard/src/main/home/dashboard/ClusterList.tsx

@@ -322,8 +322,8 @@ const TemplateList = styled.div`
 `;
 
 const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
+  font-size: 20px;
+  font-weight: 500;
   font-family: "Work Sans", sans-serif;
   color: #ffffff;
   white-space: nowrap;

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

@@ -12,6 +12,7 @@ import { RouteComponentProps, withRouter } from "react-router";
 import TabRegion from "components/TabRegion";
 import Provisioner from "../provisioner/Provisioner";
 import FormDebugger from "components/values-form/FormDebugger";
+import TitleSection from "components/TitleSection";
 
 import { pushQueryParams, pushFiltered } from "shared/routing";
 import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
@@ -157,7 +158,7 @@ class Dashboard extends Component<PropsType, StateType> {
                       {currentProject && currentProject.name[0].toUpperCase()}
                     </Overlay>
                   </DashboardIcon>
-                  <Title>{currentProject && currentProject.name}</Title>
+                  {currentProject && currentProject.name}
                   {this.context.currentProject?.roles?.filter((obj: any) => {
                     return obj.user_id === this.context.user.userId;
                   })[0].kind === "admin" || (
@@ -169,6 +170,7 @@ class Dashboard extends Component<PropsType, StateType> {
                     </i>
                   )}
                 </TitleSection>
+                <Br />
 
                 <InfoSection>
                   <TopRow>
@@ -201,6 +203,11 @@ Dashboard.contextType = Context;
 
 export default withRouter(withAuth(Dashboard));
 
+const Br = styled.div`
+  width: 100%;
+  height: 1px;
+`;
+
 const DashboardWrapper = styled.div`
   padding-bottom: 100px;
 `;
@@ -288,6 +295,7 @@ const DashboardImage = styled.img`
 const DashboardIcon = styled.div`
   position: relative;
   height: 45px;
+  margin-right: 17px;
   width: 45px;
   border-radius: 5px;
   display: flex;
@@ -298,37 +306,3 @@ const DashboardIcon = styled.div`
     font-size: 22px;
   }
 `;
-
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 18px;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  height: 80px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding-left: 0px;
-
-  > i {
-    margin-left: 10px;
-    cursor: pointer;
-    font-size: 18px;
-    color: #858faaaa;
-    padding: 5px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-    margin-bottom: -3px;
-  }
-`;

+ 29 - 61
dashboard/src/main/home/integrations/IntegrationCategories.tsx

@@ -8,6 +8,7 @@ import { RouteComponentProps, withRouter } from "react-router";
 import IntegrationList from "./IntegrationList";
 import api from "shared/api";
 import { pushFiltered } from "shared/routing";
+import TitleSection from "components/TitleSection";
 
 type PropsType = RouteComponentProps & {
   category: string;
@@ -125,20 +126,16 @@ class IntegrationCategories extends Component<PropsType, StateType> {
       integrationList[currentCategory].buttonText;
     if (currentCategory !== "repo") {
       return (
-        <div>
-          <TitleSectionAlt>
-            <Flex>
-              <i
-                className="material-icons"
-                onClick={() =>
-                  pushFiltered(this.props, "/integrations", ["project_id"])
-                }
-              >
-                keyboard_backspace
-              </i>
-              <Icon src={icon && icon} />
-              <Title>{label}</Title>
-            </Flex>
+        <>
+          <Flex>
+            <TitleSection
+              handleNavBack={() =>
+                pushFiltered(this.props, "/integrations", ["project_id"])
+              }
+              icon={icon}
+            >
+              {label}
+            </TitleSection>
             <Button
               onClick={() =>
                 this.context.setCurrentModal("IntegrationsModal", {
@@ -155,9 +152,7 @@ class IntegrationCategories extends Component<PropsType, StateType> {
               <i className="material-icons">add</i>
               {buttonText}
             </Button>
-          </TitleSectionAlt>
-
-          <LineBreak />
+          </Flex>
 
           <IntegrationList
             currentCategory={currentCategory}
@@ -168,24 +163,20 @@ class IntegrationCategories extends Component<PropsType, StateType> {
               this.getIntegrationsForCategory(this.props.category)
             }
           />
-        </div>
+        </>
       );
     } else {
       return (
-        <div>
-          <TitleSectionAlt>
-            <Flex>
-              <i
-                className="material-icons"
-                onClick={() =>
-                  pushFiltered(this.props, "/integrations", ["project_id"])
-                }
-              >
-                keyboard_backspace
-              </i>
-              <Icon src={icon && icon} />
-              <Title>{label}</Title>
-            </Flex>
+        <>
+          <Flex>
+            <TitleSection
+              handleNavBack={() =>
+                pushFiltered(this.props, "/integrations", ["project_id"])
+              }
+              icon={icon}
+            >
+              {label}
+            </TitleSection>
             <Button
               onClick={() =>
                 window.open(
@@ -196,9 +187,7 @@ class IntegrationCategories extends Component<PropsType, StateType> {
               <GHIcon />
               {buttonText}
             </Button>
-          </TitleSectionAlt>
-
-          <LineBreak />
+          </Flex>
 
           <IntegrationList
             currentCategory={currentCategory}
@@ -209,7 +198,7 @@ class IntegrationCategories extends Component<PropsType, StateType> {
               this.getIntegrationsForCategory(this.props.category)
             }
           />
-        </div>
+        </>
       );
     }
   };
@@ -228,6 +217,8 @@ const Icon = styled.img`
 const Flex = styled.div`
   display: flex;
   align-items: center;
+  margin-bottom: -20px;
+  justify-content: space-between;
 
   > i {
     cursor: pointer;
@@ -244,6 +235,7 @@ const Flex = styled.div`
 
 const Button = styled.div`
   height: 100%;
+  margin-top: -12px;
   background: #616feecc;
   :hover {
     background: #505edddd;
@@ -271,33 +263,9 @@ const Button = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  margin-bottom: 20px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  height: 40px;
-`;
-
-const TitleSectionAlt = styled(TitleSection)`
-  margin-left: -42px;
-  width: calc(100% + 42px);
-`;
-
 const LineBreak = styled.div`
   width: calc(100% - 0px);
   height: 2px;
   background: #ffffff20;
-  margin: 32px 0px 24px;
+  margin: 18px 0px 24px;
 `;

+ 19 - 40
dashboard/src/main/home/integrations/Integrations.tsx

@@ -8,6 +8,7 @@ import { pushFiltered } from "shared/routing";
 import CreateIntegrationForm from "./create-integration/CreateIntegrationForm";
 import IntegrationCategories from "./IntegrationCategories";
 import IntegrationList from "./IntegrationList";
+import TitleSection from "components/TitleSection";
 
 type PropsType = RouteComponentProps;
 
@@ -36,22 +37,17 @@ class Integrations extends Component<PropsType, StateType> {
               integrationList[integration] && integrationList[integration].icon;
             return (
               <div>
-                <TitleSectionAlt>
-                  <Flex>
-                    <i
-                      className="material-icons"
-                      onClick={() =>
-                        pushFiltered(this.props, `/integrations/${category}`, [
-                          "project_id",
-                        ])
-                      }
-                    >
-                      keyboard_backspace
-                    </i>
-                    <Icon src={icon && icon} />
-                    <Title>{integrationList[integration].label}</Title>
-                  </Flex>
-                </TitleSectionAlt>
+                <TitleSection 
+                  icon={icon}
+                  handleNavBack={() =>
+                    pushFiltered(this.props, `/integrations/${category}`, [
+                      "project_id",
+                    ])
+                  }
+                >
+                  {integrationList[integration].label}
+                </TitleSection>
+                <Buffer />
                 <CreateIntegrationForm
                   integrationName={integration}
                   closeForm={() => {
@@ -77,9 +73,7 @@ class Integrations extends Component<PropsType, StateType> {
         />
         <Route>
           <div>
-            <TitleSection>
-              <Title>Integrations</Title>
-            </TitleSection>
+            <TitleSection>Integrations</TitleSection>
 
             <IntegrationList
               currentCategory={""}
@@ -99,6 +93,11 @@ class Integrations extends Component<PropsType, StateType> {
 
 export default withRouter(Integrations);
 
+const Buffer = styled.div`
+  width: 100%;
+  height: 10px;
+`;
+
 const Br = styled.div`
   width: 100%;
   height: 150px;
@@ -127,32 +126,12 @@ const Flex = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  margin-bottom: 20px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  height: 40px;
-`;
-
 const TitleSectionAlt = styled(TitleSection)`
   margin-left: -42px;
   width: calc(100% + 42px);
 `;
 
 const StyledIntegrations = styled.div`
-  width: calc(90% - 150px);
+  width: 83%;
   min-width: 300px;
-  padding-top: 75px;
 `;

+ 3 - 34
dashboard/src/main/home/launch/Launch.tsx

@@ -10,6 +10,7 @@ import ExpandedTemplate from "./expanded-template/ExpandedTemplate";
 import Loading from "components/Loading";
 import LaunchFlow from "./launch-flow/LaunchFlow";
 import NoClusterPlaceholder from "../NoClusterPlaceholder";
+import TitleSection from "components/TitleSection";
 
 import { hardcodedNames } from "shared/hardcodedNameDict";
 import semver from "semver";
@@ -235,7 +236,7 @@ export default class Templates extends Component<PropsType, StateType> {
       return (
         <TemplatesWrapper>
           <TitleSection>
-            <Title>Launch</Title>
+            Launch
             <a href="https://docs.getporter.dev/docs/add-ons" target="_blank">
               <i className="material-icons">help_outline</i>
             </a>
@@ -399,39 +400,7 @@ const TemplateList = styled.div`
   grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  margin-bottom: 20px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-
-  > a {
-    > i {
-      display: flex;
-      align-items: center;
-      margin-bottom: -2px;
-      font-size: 18px;
-      margin-left: 15px;
-      color: #858faaaa;
-      :hover {
-        color: #aaaabb;
-      }
-    }
-  }
-`;
-
 const TemplatesWrapper = styled.div`
-  width: calc(90% - 130px);
+  width: 83%;
   min-width: 300px;
-  padding-top: 75px;
 `;

+ 0 - 1040
dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx

@@ -1,1040 +0,0 @@
-import React, { Component } from "react";
-import styled from "styled-components";
-import randomWords from "random-words";
-import _ from "lodash";
-import { Context } from "shared/Context";
-import api from "shared/api";
-import { pushFiltered } from "shared/routing";
-import close from "assets/close.png";
-import { RouteComponentProps, withRouter } from "react-router";
-
-import {
-  ActionConfigType,
-  ChoiceType,
-  ClusterType,
-  StorageType,
-} from "shared/types";
-import Selector from "components/Selector";
-import ImageSelector from "components/image-selector/ImageSelector";
-import TabRegion from "components/TabRegion";
-import InputRow from "components/values-form/InputRow";
-import SaveButton from "components/SaveButton";
-import ActionConfEditor from "components/repo-selector/ActionConfEditor";
-import FormWrapper from "components/values-form/FormWrapper";
-import RadioSelector from "components/RadioSelector";
-import { isAlphanumeric } from "shared/common";
-
-type PropsType = RouteComponentProps & {
-  currentTemplate: any;
-  currentTab: string;
-  hideLaunch: () => void;
-  values: any;
-  form: any;
-  hideBackButton?: boolean;
-};
-
-type StateType = {
-  currentView: string;
-  clusterOptions: { label: string; value: string }[];
-  clusterMap: { [clusterId: string]: ClusterType };
-  saveValuesStatus: string | null;
-  selectedNamespace: string;
-  selectedCluster: string;
-  selectedClusterId: number;
-  selectedImageUrl: string | null;
-  sourceType: string;
-  selectedTag: string | null;
-  templateName: string;
-  tabOptions: ChoiceType[];
-  currentTab: string | null;
-  tabContents: any;
-  namespaceOptions: { label: string; value: string }[];
-  actionConfig: ActionConfigType;
-  procfileProcess: string;
-  branch: string;
-  repoType: string;
-  dockerfilePath: string | null;
-  procfilePath: string | null;
-  folderPath: string | null;
-  selectedRegistry: any | null;
-  env: any;
-  valuesToOverride: any | null;
-};
-
-const defaultActionConfig: ActionConfigType = {
-  git_repo: "",
-  image_repo_uri: "",
-  branch: "",
-  git_repo_id: 0,
-};
-
-class LaunchTemplate extends Component<PropsType, StateType> {
-  state = {
-    currentView: "repo",
-    clusterOptions: [] as { label: string; value: string }[],
-    clusterMap: {} as { [clusterId: string]: ClusterType },
-    saveValuesStatus: "" as string | null,
-    selectedCluster: this.context.currentCluster.name,
-    selectedClusterId: this.context.currentCluster.id,
-    selectedNamespace: "default",
-    selectedImageUrl: "" as string | null,
-    sourceType: "",
-    templateName: "",
-    selectedTag: "" as string | null,
-    tabOptions: [] as ChoiceType[],
-    currentTab: null as string | null,
-    tabContents: [] as any,
-    namespaceOptions: [] as { label: string; value: string }[],
-    actionConfig: { ...defaultActionConfig },
-    branch: "",
-    repoType: "",
-    dockerfilePath: null as string | null,
-    procfileProcess: null as string | null,
-    procfilePath: null as string | null,
-    folderPath: null as string | null,
-    selectedRegistry: null as any | null,
-    env: {},
-    valuesToOverride: null as any | null,
-  };
-
-  createGHAction = (chartName: string, chartNamespace: string) => {
-    let { currentProject, currentCluster } = this.context;
-    let { actionConfig } = this.state;
-    let imageRepoUri = `${this.state.selectedRegistry.url}/${chartName}-${chartNamespace}`;
-
-    // DockerHub registry integration is per repo
-    if (this.state.selectedRegistry.service === "dockerhub") {
-      imageRepoUri = this.state.selectedRegistry.url;
-    }
-
-    api
-      .createGHAction(
-        "<token>",
-        {
-          git_repo: actionConfig.git_repo,
-          git_branch: this.state.branch,
-          registry_id: this.state.selectedRegistry.id,
-          dockerfile_path: this.state.dockerfilePath,
-          folder_path: this.state.folderPath,
-          image_repo_uri: imageRepoUri,
-          git_repo_id: actionConfig.git_repo_id,
-          env: this.state.env,
-        },
-        {
-          project_id: currentProject.id,
-          CLUSTER_ID: currentCluster.id,
-          RELEASE_NAME: chartName,
-          RELEASE_NAMESPACE: chartNamespace,
-        }
-      )
-      .then((res) => console.log(""))
-      .catch(console.log);
-  };
-
-  onSubmitAddon = (wildcard?: any) => {
-    let { currentCluster, currentProject, setCurrentError } = this.context;
-    let name =
-      this.state.templateName || randomWords({ exactly: 3, join: "-" });
-    this.setState({ saveValuesStatus: "loading" });
-
-    let values = {};
-    for (let key in wildcard) {
-      _.set(values, key, wildcard[key]);
-    }
-
-    api
-      .deployTemplate(
-        "<token>",
-        {
-          templateName: this.props.currentTemplate.name,
-          storage: StorageType.Secret,
-          formValues: values,
-          namespace: this.state.selectedNamespace,
-          name,
-        },
-        {
-          id: currentProject.id,
-          cluster_id: currentCluster.id,
-          name: this.props.currentTemplate.name.toLowerCase().trim(),
-          version: this.props.currentTemplate?.currentVersion || "latest",
-          repo_url: process.env.ADDON_CHART_REPO_URL,
-        }
-      )
-      .then((_) => {
-        // this.props.setCurrentView('cluster-dashboard');
-        this.setState({ saveValuesStatus: "successful" }, () => {
-          // TODO: redirect to appropriate cluster if not current context
-          let dst =
-            this.props.currentTemplate.name === "job"
-              ? "/jobs"
-              : "/applications";
-          setTimeout(() => {
-            pushFiltered(this.props, dst, ["project_id"], {
-              cluster: currentCluster.name,
-            });
-          }, 500);
-          window.analytics.track("Deployed Add-on", {
-            name: this.props.currentTemplate.name,
-            namespace: this.state.selectedNamespace,
-            values: values,
-          });
-        });
-      })
-      .catch((err) => {
-        let parsedErr =
-          err?.response?.data?.errors && err.response.data.errors[0];
-        if (parsedErr) {
-          err = parsedErr;
-        }
-
-        this.setState({
-          saveValuesStatus: parsedErr,
-        });
-
-        setCurrentError(err);
-
-        window.analytics.track("Failed to Deploy Add-on", {
-          name: this.props.currentTemplate.name,
-          namespace: this.state.selectedNamespace,
-          values: values,
-          error: err,
-        });
-      });
-  };
-
-  onSubmit = async (rawValues: any) => {
-    let { currentCluster, currentProject } = this.context;
-    let name =
-      this.state.templateName || randomWords({ exactly: 3, join: "-" });
-    this.setState({ saveValuesStatus: "loading" });
-
-    // Convert dotted keys to nested objects
-    let values: any = {};
-    for (let key in rawValues) {
-      _.set(values, key, rawValues[key]);
-    }
-
-    let imageUrl = this.state.selectedImageUrl;
-    let tag = this.state.selectedTag;
-
-    if (this.state.selectedImageUrl.includes(":")) {
-      let splits = this.state.selectedImageUrl.split(":");
-      imageUrl = splits[0];
-      tag = splits[1];
-    } else if (!tag) {
-      tag = "latest";
-    }
-
-    if (this.state.sourceType === "repo") {
-      if (this.props.currentTemplate?.name == "job") {
-        imageUrl = "public.ecr.aws/o1j4x7p4/hello-porter-job";
-        tag = "latest";
-      } else {
-        imageUrl = "public.ecr.aws/o1j4x7p4/hello-porter";
-        tag = "latest";
-      }
-    }
-
-    let provider;
-    switch (currentCluster.service) {
-      case "eks":
-        provider = "aws";
-        break;
-      case "gke":
-        provider = "gcp";
-        break;
-      case "doks":
-        provider = "digitalocean";
-        break;
-      default:
-        provider = "";
-    }
-
-    // don't overwrite for templates that already have a source (i.e. non-Docker templates)
-    if (imageUrl && tag) {
-      _.set(values, "image.repository", imageUrl);
-      _.set(values, "image.tag", tag);
-    }
-
-    _.set(values, "ingress.provider", provider);
-    var url: string;
-    // check if template is docker and create external domain if necessary
-    if (this.props.currentTemplate.name == "web") {
-      if (values?.ingress?.enabled && !values?.ingress?.custom_domain) {
-        url = await new Promise((resolve, reject) => {
-          api
-            .createSubdomain(
-              "<token>",
-              {
-                release_name: name,
-              },
-              {
-                id: currentProject.id,
-                cluster_id: currentCluster.id,
-              }
-            )
-            .then((res) => {
-              resolve(res.data?.external_url);
-            })
-            .catch((err) => {
-              this.setState({ saveValuesStatus: "error" });
-            });
-        });
-
-        values.ingress.porter_hosts = [url];
-      }
-    }
-
-    api
-      .deployTemplate(
-        "<token>",
-        {
-          templateName: this.props.currentTemplate.name,
-          imageURL: this.state.selectedImageUrl,
-          storage: StorageType.Secret,
-          formValues: values,
-          namespace: this.state.selectedNamespace,
-          name,
-        },
-        {
-          id: currentProject.id,
-          cluster_id: currentCluster.id,
-          name: this.props.currentTemplate.name.toLowerCase().trim(),
-          version: this.props.currentTemplate?.currentVersion || "latest",
-          repo_url: process.env.APPLICATION_CHART_REPO_URL,
-        }
-      )
-      .then((_) => {
-        console.log("Deployed template.");
-        if (this.state.sourceType === "repo") {
-          console.log("Creating GHA");
-          this.createGHAction(name, this.state.selectedNamespace);
-        }
-        // this.props.setCurrentView('cluster-dashboard');
-        this.setState({ saveValuesStatus: "successful" }, () => {
-          // redirect to dashboard with namespace
-          setTimeout(() => {
-            let dst =
-              this.props.currentTemplate.name === "job"
-                ? "/jobs"
-                : "/applications";
-            pushFiltered(this.props, dst, ["project_id"], {
-              cluster: currentCluster.name,
-            });
-          }, 1000);
-        });
-      })
-      .catch((err) => {
-        this.setState({ saveValuesStatus: "error" });
-      });
-  };
-
-  submitIsDisabled = () => {
-    let {
-      templateName,
-      sourceType,
-      selectedImageUrl,
-      dockerfilePath,
-      folderPath,
-    } = this.state;
-
-    // Allow if name is invalid
-    if (templateName.length > 0 && !isAlphanumeric(templateName)) {
-      return true;
-    }
-
-    if (this.state.saveValuesStatus == "loading") {
-      return true;
-    }
-
-    if (this.props.form?.hasSource) {
-      // Allow if source type is registry and image URL is specified
-      if (sourceType === "registry" && selectedImageUrl) {
-        return false;
-      }
-
-      // Allow if source type is repo and dockerfile or folder path is set
-      if (sourceType === "repo" && (dockerfilePath || folderPath)) {
-        return !this.state.selectedRegistry;
-      }
-
-      return true;
-    } else {
-      return false;
-    }
-  };
-
-  getStatus = () => {
-    let {
-      selectedRegistry,
-      sourceType,
-      dockerfilePath,
-      folderPath,
-      procfilePath,
-    } = this.state;
-
-    if (!this.submitIsDisabled()) {
-      return this.state.saveValuesStatus;
-    }
-
-    // handle exception when deploy process is on loading
-    if (this.state.saveValuesStatus === "loading") {
-      return "loading";
-    }
-
-    if (
-      sourceType === "repo" &&
-      (dockerfilePath || folderPath) &&
-      !selectedRegistry
-    ) {
-      return "A connected container registry is required";
-    }
-    let { templateName } = this.state;
-    if (templateName.length > 0 && !isAlphanumeric(templateName)) {
-      return "Template name contains illegal characters";
-    }
-    return "No application source specified";
-  };
-
-  componentDidMount() {
-    if (this.props.currentTemplate.name !== "docker") {
-      this.setState({ saveValuesStatus: "" });
-    }
-    // Retrieve tab options
-    let tabOptions = [] as ChoiceType[];
-    this.props.form?.tabs.map((tab: any, i: number) => {
-      if (tab.context.type === "helm/values") {
-        tabOptions.push({ value: tab.name, label: tab.label });
-      }
-    });
-
-    this.setState({
-      tabOptions,
-      currentTab: tabOptions[0] && tabOptions[0]["value"],
-    });
-
-    // TODO: query with selected filter once implemented
-    let { currentProject, currentCluster } = this.context;
-    api.getClusters("<token>", {}, { id: currentProject.id }).then((res) => {
-      if (res.data) {
-        let clusterOptions: { label: string; value: string }[] = [];
-        let clusterMap: { [clusterId: string]: ClusterType } = {};
-        res.data.forEach((cluster: ClusterType, i: number) => {
-          clusterOptions.push({ label: cluster.name, value: cluster.name });
-          clusterMap[cluster.name] = cluster;
-        });
-        if (res.data.length > 0) {
-          this.setState({ clusterOptions, clusterMap });
-        }
-      }
-    });
-
-    this.updateNamespaces(currentCluster.id);
-  }
-
-  updateNamespaces = (id: number) => {
-    let { currentProject } = this.context;
-    api
-      .getNamespaces(
-        "<token>",
-        {
-          cluster_id: id,
-        },
-        { id: currentProject.id }
-      )
-      .then((res) => {
-        if (res.data) {
-          const availableNamespaces = res.data.items.filter(
-            (namespace: any) => {
-              return namespace.status.phase !== "Terminating";
-            }
-          );
-          const namespaceOptions = availableNamespaces.map(
-            (x: { metadata: { name: string } }) => {
-              return { label: x.metadata.name, value: x.metadata.name };
-            }
-          );
-          if (availableNamespaces.length > 0) {
-            this.setState({ namespaceOptions });
-          }
-        }
-      })
-      .catch(console.log);
-  };
-
-  setSelectedImageUrl = (x: string) => {
-    this.setState({ selectedImageUrl: x });
-  };
-
-  renderIcon = (icon: string) => {
-    if (icon) {
-      return <Icon src={icon} />;
-    }
-
-    return (
-      <Polymer>
-        <i className="material-icons">layers</i>
-      </Polymer>
-    );
-  };
-
-  renderSettingsRegion = () => {
-    if (this.state.tabOptions.length > 0) {
-      return (
-        <>
-          <Heading>Additional Settings</Heading>
-          <Subtitle>
-            Configure additional settings for this template. (Optional)
-          </Subtitle>
-          <FormWrapper
-            formData={this.props.form}
-            saveValuesStatus={this.state.saveValuesStatus}
-            valuesToOverride={this.state.valuesToOverride}
-            clearValuesToOverride={() =>
-              this.setState({ valuesToOverride: null })
-            }
-            externalValues={{
-              namespace: this.state.selectedNamespace,
-              clusterId: this.context.currentCluster.id,
-              isLaunch: true,
-            }}
-            onSubmit={
-              this.props.currentTab === "docker"
-                ? this.onSubmit
-                : this.onSubmitAddon
-            }
-          />
-        </>
-      );
-    } else {
-      return (
-        <Wrapper>
-          <Placeholder>
-            To configure this chart through Porter,
-            <Link
-              target="_blank"
-              href="https://docs.getporter.dev/docs/add-ons"
-            >
-              refer to our docs
-            </Link>
-            .
-          </Placeholder>
-          <SaveButton
-            text="Deploy"
-            onClick={this.onSubmitAddon}
-            status={this.state.saveValuesStatus}
-            makeFlush={true}
-          />
-        </Wrapper>
-      );
-    }
-  };
-
-  // Display if current template uses source (image or repo)
-  renderSourceSelectorContent = () => {
-    let { capabilities } = this.context;
-
-    if (this.state.sourceType === "") {
-      return (
-        <BlockList>
-          {capabilities.github && (
-            <Block
-              onClick={() => {
-                this.setState({ sourceType: "repo" });
-              }}
-            >
-              <BlockIcon src="https://3.bp.blogspot.com/-xhNpNJJyQhk/XIe4GY78RQI/AAAAAAAAItc/ouueFUj2Hqo5dntmnKqEaBJR4KQ4Q2K3ACK4BGAYYCw/s1600/logo%2Bgit%2Bicon.png" />
-              <BlockTitle>Git Repository</BlockTitle>
-              <BlockDescription>
-                Deploy using source from a Git repo.
-              </BlockDescription>
-            </Block>
-          )}
-          <Block
-            onClick={() => {
-              this.setState({ sourceType: "registry" });
-            }}
-          >
-            <BlockIcon src="https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png" />
-            <BlockTitle>Docker Registry</BlockTitle>
-            <BlockDescription>
-              Deploy a container from an image registry.
-            </BlockDescription>
-          </Block>
-        </BlockList>
-      );
-    } else if (this.state.sourceType === "registry") {
-      return (
-        <StyledSourceBox>
-          <CloseButton
-            onClick={() =>
-              this.setState({
-                sourceType: "",
-                selectedImageUrl: "",
-                selectedTag: "",
-              })
-            }
-          >
-            <CloseButtonImg src={close} />
-          </CloseButton>
-          <Subtitle>
-            Specify the container image you would like to connect to this
-            template.
-            <Highlight
-              onClick={() =>
-                pushFiltered(this.props, "/integrations/registry", [
-                  "project_id",
-                ])
-              }
-            >
-              Manage Docker registries
-            </Highlight>
-            <Required>*</Required>
-          </Subtitle>
-          <DarkMatter antiHeight="-4px" />
-          <ImageSelector
-            selectedTag={this.state.selectedTag}
-            selectedImageUrl={this.state.selectedImageUrl}
-            setSelectedImageUrl={this.setSelectedImageUrl}
-            setSelectedTag={(x: string) => this.setState({ selectedTag: x })}
-            forceExpanded={true}
-          />
-          <br />
-        </StyledSourceBox>
-      );
-    } else {
-      return (
-        <StyledSourceBox>
-          <CloseButton onClick={() => this.setState({ sourceType: "" })}>
-            <CloseButtonImg src={close} />
-          </CloseButton>
-          <Subtitle>
-            Provide a repo folder to use as source.
-            <Highlight
-              onClick={() =>
-                pushFiltered(this.props, "/integrations/repo", ["project_id"])
-              }
-            >
-              Manage Git repos
-            </Highlight>
-            <Required>*</Required>
-          </Subtitle>
-          <DarkMatter antiHeight="-4px" />
-          <ActionConfEditor
-            actionConfig={this.state.actionConfig}
-            branch={this.state.branch}
-            setActionConfig={(actionConfig: ActionConfigType) =>
-              this.setState({ actionConfig }, () => {
-                this.setSelectedImageUrl(
-                  this.state.actionConfig.image_repo_uri
-                );
-              })
-            }
-            procfileProcess={this.state.procfileProcess}
-            setProcfileProcess={(procfileProcess: string) =>
-              this.setState({
-                procfileProcess,
-                valuesToOverride: {
-                  "container.command": {
-                    value: procfileProcess || "",
-                  },
-                  showStartCommand: {
-                    value: !procfileProcess,
-                  },
-                },
-              })
-            }
-            setBranch={(branch: string) => this.setState({ branch })}
-            setDockerfilePath={(x: string) =>
-              this.setState({ dockerfilePath: x })
-            }
-            setProcfilePath={(x: string) => {
-              this.setState({ procfilePath: x });
-            }}
-            procfilePath={this.state.procfilePath}
-            dockerfilePath={this.state.dockerfilePath}
-            folderPath={this.state.folderPath}
-            setFolderPath={(x: string) => this.setState({ folderPath: x })}
-            reset={() => {
-              this.setState({
-                actionConfig: { ...defaultActionConfig },
-                branch: "",
-                dockerfilePath: null,
-                folderPath: null,
-              });
-            }}
-            setSelectedRegistry={(x: any) => {
-              this.setState({ selectedRegistry: x });
-            }}
-            selectedRegistry={this.state.selectedRegistry}
-          />
-          <br />
-        </StyledSourceBox>
-      );
-    }
-  };
-
-  renderSourceSelector = () => {
-    return (
-      <>
-        <Heading>Deployment Method</Heading>
-        <Subtitle>
-          Choose the deployment method you would like to use for this
-          application.
-          <Required>*</Required>
-        </Subtitle>
-        {this.renderSourceSelectorContent()}
-      </>
-    );
-  };
-
-  render() {
-    console.log("RENDERING");
-    let { name, icon } = this.props.currentTemplate;
-    let { currentTemplate } = this.props;
-
-    return (
-      <StyledLaunchTemplate>
-        <HeaderSection>
-          <i className="material-icons" onClick={this.props.hideLaunch}>
-            keyboard_backspace
-          </i>
-          {icon ? this.renderIcon(icon) : this.renderIcon(currentTemplate.icon)}
-          <Title>{name}</Title>
-        </HeaderSection>
-        <DarkMatter antiHeight="-13px" />
-        <Heading isAtTop={true}>Name</Heading>
-        <Subtitle>
-          Randomly generated if left blank.
-          <Warning
-            highlight={
-              !isAlphanumeric(this.state.templateName) &&
-              this.state.templateName !== ""
-            }
-          >
-            Lowercase letters, numbers, and "-" only.
-          </Warning>
-        </Subtitle>
-        <DarkMatter antiHeight="-29px" />
-        <InputRow
-          type="text"
-          value={this.state.templateName}
-          setValue={(x: string) => this.setState({ templateName: x })}
-          placeholder="ex: doctor-scientist"
-          width="100%"
-        />
-
-        {this.props.form?.hasSource && this.renderSourceSelector()}
-
-        <Heading>Destination</Heading>
-        <Subtitle>
-          Specify the cluster and namespace you would like to deploy your
-          application to.
-        </Subtitle>
-        <ClusterSection>
-          <ClusterLabel>
-            <i className="material-icons">device_hub</i>Cluster
-          </ClusterLabel>
-          <Selector
-            activeValue={this.state.selectedCluster}
-            setActiveValue={(cluster: string) => {
-              this.context.setCurrentCluster(this.state.clusterMap[cluster]);
-              this.updateNamespaces(this.state.clusterMap[cluster].id);
-              this.setState({
-                selectedCluster: cluster,
-                selectedClusterId: this.state.clusterMap[cluster].id,
-              });
-            }}
-            options={this.state.clusterOptions}
-            width="250px"
-            dropdownWidth="335px"
-            closeOverlay={true}
-          />
-          <NamespaceLabel>
-            <i className="material-icons">view_list</i>Namespace
-          </NamespaceLabel>
-          <Selector
-            key={"namespace"}
-            activeValue={this.state.selectedNamespace}
-            setActiveValue={(namespace: string) =>
-              this.setState({ selectedNamespace: namespace })
-            }
-            addButton={true}
-            options={this.state.namespaceOptions}
-            width="250px"
-            dropdownWidth="335px"
-            closeOverlay={true}
-          />
-        </ClusterSection>
-        {this.renderSettingsRegion()}
-      </StyledLaunchTemplate>
-    );
-  }
-}
-
-LaunchTemplate.contextType = Context;
-export default withRouter(LaunchTemplate);
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  z-index: 1;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
-const BlockIcon = styled.img<{ bw?: boolean }>`
-  height: 38px;
-  padding: 2px;
-  margin-top: 30px;
-  margin-bottom: 15px;
-  filter: ${(props) => (props.bw ? "grayscale(1)" : "")};
-`;
-
-const BlockDescription = styled.div`
-  margin-bottom: 12px;
-  color: #ffffff66;
-  text-align: center;
-  font-weight: default;
-  font-size: 13px;
-  padding: 0px 25px;
-  height: 2.4em;
-  font-size: 12px;
-  display: -webkit-box;
-  overflow: hidden;
-  -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
-`;
-
-const BlockTitle = styled.div`
-  margin-bottom: 12px;
-  width: 80%;
-  text-align: center;
-  font-size: 14px;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const Block = styled.div<{ disabled?: boolean }>`
-  align-items: center;
-  user-select: none;
-  border-radius: 5px;
-  display: flex;
-  font-size: 13px;
-  overflow: hidden;
-  font-weight: 500;
-  padding: 3px 0px 12px;
-  flex-direction: column;
-  align-item: center;
-  justify-content: space-between;
-  height: 170px;
-  cursor: ${(props) => (props.disabled ? "" : "pointer")};
-  color: #ffffff;
-  position: relative;
-  background: #26282f;
-  box-shadow: 0 3px 5px 0px #00000022;
-  :hover {
-    background: ${(props) => (props.disabled ? "" : "#ffffff11")};
-  }
-
-  animation: fadeIn 0.3s 0s;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
-const BlockList = styled.div`
-  overflow: visible;
-  margin-top: 6px;
-  margin-bottom: 27px;
-  display: grid;
-  grid-column-gap: 25px;
-  grid-row-gap: 25px;
-  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-`;
-
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 10px;
-  border-radius: 2px;
-  color: #ffffff;
-`;
-
-const HeaderSection = styled.div`
-  display: flex;
-  align-items: center;
-  margin-bottom: 30px;
-
-  > i {
-    cursor: pointer;
-    font-size 24px;
-    color: #969Fbbaa;
-    padding: 3px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-  }
-`;
-
-const Heading = styled.div<{ isAtTop?: boolean }>`
-  color: white;
-  font-weight: 500;
-  font-size: 16px;
-  margin-bottom: 5px;
-  margin-top: ${(props) => (props.isAtTop ? "10px" : "30px")};
-  display: flex;
-  align-items: center;
-`;
-
-const Warning = styled.span<{ highlight: boolean; makeFlush?: boolean }>`
-  color: ${(props) => (props.highlight ? "#f5cb42" : "")};
-  margin-left: ${(props) => (props.makeFlush ? "" : "5px")};
-`;
-
-const Required = styled.div`
-  margin-left: 8px;
-  color: #fc4976;
-  display: inline-block;
-`;
-
-const Link = styled.a`
-  margin-left: 5px;
-`;
-
-const Wrapper = styled.div`
-  width: 100%;
-  position: relative;
-  padding-top: 20px;
-  padding-bottom: 70px;
-`;
-
-const Placeholder = styled.div`
-  width: 100%;
-  height: 200px;
-  background: #ffffff11;
-  border: 1px solid #ffffff44;
-  border-radius: 5px;
-  color: #aaaabb;
-  font-size: 13px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-`;
-
-const DarkMatter = styled.div<{ antiHeight?: string }>`
-  width: 100%;
-  margin-top: ${(props) => props.antiHeight || "-15px"};
-`;
-
-const Subtitle = styled.div`
-  padding: 11px 0px 16px;
-  font-family: "Work Sans", sans-serif;
-  font-size: 13px;
-  color: #aaaabb;
-  line-height: 1.6em;
-  display: flex;
-  align-items: center;
-`;
-
-const ClusterLabel = styled.div`
-  margin-right: 10px;
-  display: flex;
-  align-items: center;
-  > i {
-    font-size: 16px;
-    margin-right: 6px;
-  }
-`;
-
-const NamespaceLabel = styled.div`
-  margin-left: 15px;
-  margin-right: 10px;
-  display: flex;
-  align-items: center;
-  > i {
-    font-size: 16px;
-    margin-right: 6px;
-  }
-`;
-
-const Icon = styled.img`
-  width: 21px;
-  margin-right: 6px;
-  margin-left: 10px;
-`;
-
-const Polymer = styled.div`
-  margin-bottom: -3px;
-
-  > i {
-    color: ${(props) => props.theme.containerIcon};
-    font-size: 18px;
-    margin-right: 10px;
-  }
-`;
-
-const ClusterSection = styled.div`
-  display: flex;
-  align-items: center;
-  color: #ffffff;
-  font-family: "Work Sans", sans-serif;
-  font-size: 14px;
-  margin-top: 2px;
-  font-weight: 500;
-  margin-bottom: 32px;
-
-  > i {
-    font-size: 25px;
-    color: #ffffff44;
-    margin-right: 13px;
-  }
-`;
-
-const StyledLaunchTemplate = styled.div`
-  width: 100%;
-  padding-bottom: 150px;
-`;
-
-const Highlight = styled.div`
-  color: #8590ff;
-  text-decoration: none;
-  margin-left: 5px;
-  cursor: pointer;
-`;
-
-const StyledSourceBox = styled.div`
-  width: 100%;
-  height: 100%;
-  background: #ffffff11;
-  color: #ffffff;
-  padding: 14px 35px 20px;
-  position: relative;
-  border-radius: 5px;
-  font-size: 13px;
-  margin-top: 6px;
-  overflow: auto;
-  margin-bottom: 25px;
-`;

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

@@ -318,8 +318,8 @@ const Polymer = styled.div`
 `;
 
 const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
+  font-size: 20px;
+  font-weight: 500;
   font-family: "Work Sans", sans-serif;
   margin-left: 10px;
   border-radius: 2px;

+ 4 - 54
dashboard/src/main/home/launch/launch-flow/LaunchFlow.tsx

@@ -11,6 +11,7 @@ import { pushFiltered } from "shared/routing";
 import { hardcodedNames } from "shared/hardcodedNameDict";
 import SourcePage from "./SourcePage";
 import SettingsPage from "./SettingsPage";
+import TitleSection from "components/TitleSection";
 
 import {
   PorterTemplate,
@@ -455,14 +456,9 @@ class LaunchFlow extends Component<PropsType, StateType> {
 
     return (
       <StyledLaunchFlow>
-        <TitleSection>
-          <i className="material-icons" onClick={this.props.hideLaunchFlow}>
-            keyboard_backspace
-          </i>
+        <TitleSection handleNavBack={this.props.hideLaunchFlow}>
           {this.renderIcon()}
-          <Title>
-            New {name} {currentTab === "porter" ? null : "Instance"}
-          </Title>
+          New {name} {currentTab === "porter" ? null : "Instance"}
         </TitleSection>
         {this.renderCurrentPage()}
         <Br />
@@ -509,54 +505,8 @@ const Polymer = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  margin-bottom: 20px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-
-  > i {
-    cursor: pointer;
-    font-size 24px;
-    color: #969Fbbaa;
-    margin-right: 10px;
-    padding: 3px;
-    margin-left: 0px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-  }
-
-  > a {
-    > i {
-      display: flex;
-      align-items: center;
-      margin-bottom: -2px;
-      font-size: 18px;
-      margin-left: 18px;
-      color: #858faaaa;
-      cursor: pointer;
-      :hover {
-        color: #aaaabb;
-      }
-    }
-  }
-`;
-
 const StyledLaunchFlow = styled.div`
   width: calc(90% - 130px);
   min-width: 300px;
-  padding-top: 20px;
-  margin-top: calc(50vh - 340px);
+  margin-top: calc(50vh - 380px);
 `;

+ 4 - 28
dashboard/src/main/home/new-project/NewProject.tsx

@@ -8,6 +8,7 @@ import { isAlphanumeric } from "shared/common";
 import InputRow from "components/values-form/InputRow";
 import Helper from "components/values-form/Helper";
 import ProvisionerSettings from "../provisioner/ProvisionerSettings";
+import TitleSection from "components/TitleSection";
 
 type PropsType = {};
 
@@ -27,9 +28,7 @@ export default class NewProject extends Component<PropsType, StateType> {
     let { projectName } = this.state;
     return (
       <StyledNewProject>
-        <TitleSection>
-          <Title>New Project</Title>
-        </TitleSection>
+        <TitleSection>New Project</TitleSection>
         <Helper>
           Project name
           <Warning
@@ -130,8 +129,8 @@ const Warning = styled.span`
 `;
 
 const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
+  font-size: 20px;
+  font-weight: 500;
   font-family: "Work Sans", sans-serif;
   color: #ffffff;
   white-space: nowrap;
@@ -139,32 +138,9 @@ const Title = styled.div`
   text-overflow: ellipsis;
 `;
 
-const TitleSection = styled.div`
-  margin-bottom: 20px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-
-  > a {
-    > i {
-      display: flex;
-      align-items: center;
-      margin-bottom: -2px;
-      font-size: 18px;
-      margin-left: 18px;
-      color: #858faaaa;
-      cursor: pointer;
-      :hover {
-        color: #aaaabb;
-      }
-    }
-  }
-`;
-
 const StyledNewProject = styled.div`
   width: calc(90% - 130px);
   min-width: 300px;
   position: relative;
-  padding-top: 50px;
   margin-top: calc(50vh - 340px);
 `;

+ 3 - 23
dashboard/src/main/home/project-settings/ProjectSettings.tsx

@@ -7,6 +7,7 @@ import InvitePage from "./InviteList";
 import TabRegion from "components/TabRegion";
 import Heading from "components/values-form/Heading";
 import Helper from "components/values-form/Helper";
+import TitleSection from "components/TitleSection";
 import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
 
 type PropsType = WithAuthProps & {};
@@ -91,9 +92,7 @@ class ProjectSettings extends Component<PropsType, StateType> {
   render() {
     return (
       <StyledProjectSettings>
-        <TitleSection>
-          <Title>Project Settings</Title>
-        </TitleSection>
+        <TitleSection>Project Settings</TitleSection>
         <TabRegion
           currentTab={this.state.currentTab}
           setCurrentTab={(x: string) => this.setState({ currentTab: x })}
@@ -117,28 +116,9 @@ const Warning = styled.div`
   margin-bottom: 20px;
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  margin-bottom: 13px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  height: 40px;
-`;
-
 const StyledProjectSettings = styled.div`
-  width: calc(90% - 130px);
+  width: 83%;
   min-width: 300px;
-  padding-top: 70px;
   height: 100vh;
 `;