Explorar el Código

resource tabs refactored + manifests tab sidetabs

jusrhee hace 5 años
padre
commit
d3a5f1f1e0

+ 260 - 0
dashboard/src/components/ResourceTab.tsx

@@ -0,0 +1,260 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import { kindToIcon } from '../shared/rosettaStone';
+
+type PropsType = {
+  kind: string,
+  name: string,
+  handleClick?: () => void,
+  selected?: boolean,
+  isLast?: boolean,
+  status?: {
+    label: string,
+    available: number,
+    total: number,
+  } | null
+};
+
+type StateType = {
+  expanded: boolean,
+  showTooltip: boolean,
+};
+
+export default class ResourceTab extends Component<PropsType, StateType> {
+  state = {
+    expanded: false,
+    showTooltip: false,
+  }
+
+  renderDropdownIcon = () => {
+    if (this.props.children) {
+      return (
+        <DropdownIcon expanded={this.state.expanded}>
+          <i className="material-icons">arrow_right</i>
+        </DropdownIcon>
+      );
+    }
+  }
+
+  renderIcon = (kind: string) => {
+    let icon = 'tonality';
+    if (Object.keys(kindToIcon).includes(kind)) {
+      icon = kindToIcon[kind]; 
+    }
+    
+    return (
+      <IconWrapper>
+        <i className="material-icons">{icon}</i>
+      </IconWrapper>
+    );
+  }
+
+  renderTooltip = (x: string): JSX.Element | undefined => {
+    if (this.state.showTooltip) {
+      return (
+        <Tooltip>{x}</Tooltip>
+      );
+    }
+  }
+
+  renderStatus = () => {
+    let { status } = this.props;
+    if (status) {
+      return (
+        <Status>
+          <StatusColor status={status.label} />
+          {status.available}/{status.total}
+        </Status>
+      );
+    }
+  }
+
+  renderExpanded = () => {
+    if (this.props.children && this.state.expanded) {
+      return (
+        <ExpandWrapper>
+          {this.props.children}
+        </ExpandWrapper>
+      );
+    }
+  }
+
+  render() {
+    let { kind, name, children, isLast } = this.props;
+    return (
+      <StyledResourceTab 
+        isLast={isLast}
+        onClick={() => this.props.handleClick && this.props.handleClick()}
+      >
+        <ResourceHeader
+          hasChildren={this.props.children && true}
+          expanded={this.state.expanded || this.props.selected}
+          onClick={() => {
+            if (children) {
+              this.setState({ expanded: !this.state.expanded });
+            }
+          }}
+        >
+          {this.renderDropdownIcon()}
+          <Info>
+            <Metadata>
+              {this.renderIcon(kind)}
+              {kind}
+              <ResourceName
+                showKindLabels={true}
+                onMouseOver={() => { this.setState({ showTooltip: true }) }}
+                onMouseOut={() => { this.setState({ showTooltip: false }) }}
+              >
+                {name}
+              </ResourceName>
+              {this.renderTooltip(name)}
+            </Metadata>
+            {this.renderStatus()}
+          </Info>
+        </ResourceHeader>
+        {this.renderExpanded()}
+      </StyledResourceTab>
+    );
+  }
+}
+
+const StyledResourceTab = styled.div`
+  width: 100%;
+  margin-bottom: 2px;
+  background: #ffffff11;
+  border-bottom-left-radius: ${(props: { isLast: boolean }) => props.isLast ? '5px' : ''};
+`;
+
+const Tooltip = styled.div`
+  position: absolute;
+  right: 0px;
+  top: 25px;
+  height: 18px;
+  padding: 2px 5px;
+  background: #383842dd;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex: 1;
+  color: white;
+  text-transform: none;
+  font-size: 12px;
+  font-family: "Work Sans", sans-serif;
+  outline: 1px solid #ffffff55;
+  opacity: 0;
+  animation: faded-in 0.2s 0.15s;
+  animation-fill-mode: forwards;
+  @keyframes faded-in {
+    from { opacity: 0 }
+    to { opacity: 1 }
+  }
+`;
+
+const ExpandWrapper = styled.div`
+  overflow: hidden;
+`;
+
+const ResourceHeader = styled.div`
+  width: 100%;
+  height: 50px;
+  display: flex;
+  align-items: center;
+  color: #ffffff66;
+  padding: 8px 18px;
+  padding-left: ${(props: { expanded: boolean, hasChildren: boolean }) => props.hasChildren ? '10px' : '22px'};
+  text-transform: capitalize;
+  cursor: pointer;
+  background: ${(props: { expanded: boolean, hasChildren: boolean }) => props.expanded ? '#ffffff11' : ''};
+  :hover {
+    background: #ffffff18;
+
+    > i {
+      background: #ffffff22;
+    }
+  }
+`;
+
+const Info = styled.div`
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  width: calc(100% - 30px);
+  height: 100%;
+`;
+
+const Metadata = styled.div`
+  display: flex;
+  max-width: calc(100% - 50px);
+  align-items: center;
+  position: relative;
+`;
+
+const Status = styled.div`
+  display: flex;
+  width: 50px;
+  font-size: 12px;
+  text-transform: capitalize;
+  justify-content: flex-end;
+  align-items: center;
+  font-family: 'Work Sans', sans-serif;
+  color: #aaaabb;
+  animation: fadeIn 0.5s;
+  @keyframes fadeIn {
+    from { opacity: 0 }
+    to { opacity: 1 }
+  }
+`;
+
+const StatusColor = styled.div`
+  margin-right: 7px;
+  width: 7px;
+  min-width: 7px;
+  height: 7px;
+  background: ${(props: { status: string }) => (props.status === 'running' ? '#4797ff' : props.status === 'failed' ? "#ed5f85" : "#f5cb42")};
+  border-radius: 20px;
+`;
+
+const ResourceName = styled.div`
+  color: #ffffff;
+  margin-left: ${(props: { showKindLabels: boolean }) => props.showKindLabels ? '10px' : ''};
+  text-transform: none;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+`;
+
+const IconWrapper = styled.div`
+  width: 25px;
+  height: 25px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  > i {
+    font-size: 15px;
+    color: #ffffff;
+    margin-right: 14px;
+  }
+`;
+
+const DropdownIcon = styled.div`
+  > i {
+    margin-top: 2px;
+    margin-right: 11px;
+    font-size: 20px;
+    color: #ffffff66;
+    cursor: pointer;
+    border-radius: 20px;
+    background: ${(props: { expanded: boolean }) => props.expanded ? '#ffffff18' : ''};
+    transform: ${(props: { expanded: boolean }) => props.expanded ? 'rotate(180deg)' : ''};
+    animation: ${(props: { expanded: boolean }) => props.expanded ? 'quarterTurn 0.3s' : ''};
+    animation-fill-mode: forwards;
+
+    @keyframes quarterTurn {
+      from { transform: rotate(0deg) }
+      to { transform: rotate(90deg) }
+    }
+  }
+`;

+ 26 - 1
dashboard/src/components/TabSelector.tsx

@@ -40,16 +40,41 @@ export default class TabSelector extends Component<PropsType, StateType> {
     );
   }
 
+  renderAddendumBuffer = () => {
+
+  }
+
   render() {
     return (
       <StyledTabSelector>
-        {this.renderTabList()}
+        <TabWrapper>
+          {this.renderTabList()}
+          <Tab
+            lastItem={true}
+            highlight={null}
+          >
+            <Buffer />
+          </Tab>
+        </TabWrapper>
         {this.props.addendum}
       </StyledTabSelector>
     );
   }
 }
 
+const Buffer = styled.div`
+  width: 138px;
+  height: 10px;
+`;
+
+// Keeps the scrollbar beneath all tabs on overflow
+const TabWrapper = styled.div`
+  display: flex;
+  overflow-x: auto;
+  padding-bottom: 15px;
+  margin-bottom: -15px;
+`;
+
 const Tab = styled.div`
   height: 30px;
   margin-right: ${(props: { lastItem: boolean, highlight: string }) => props.lastItem ? '' : '30px'};

+ 64 - 0
dashboard/src/components/TooltipParent.tsx

@@ -0,0 +1,64 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+type PropsType = {
+  tooltipText: string
+};
+
+type StateType = {
+  showTooltip: boolean,
+};
+
+export default class TooltipParent extends Component<PropsType, StateType> {
+  state = {
+    showTooltip: false,
+  }
+
+  renderTooltip = (): JSX.Element | undefined => {
+    if (this.state.showTooltip) {
+      return (
+        <Tooltip>{this.props.tooltipText}</Tooltip>
+      );
+    }
+  }
+
+  render() {
+    return (
+      <StyledTooltipParent
+        onMouseOver={() => { this.setState({ showTooltip: true }) }}
+        onMouseOut={() => { this.setState({ showTooltip: false }) }}
+      >
+        {this.props.children}
+        {this.renderTooltip()}
+      </StyledTooltipParent>
+    );
+  }
+}
+
+const Tooltip = styled.div`
+  position: absolute;
+  left: 10px;
+  top: 20px;
+  height: 18px;
+  padding: 2px 5px;
+  background: #383842dd;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex: 1;
+  color: white;
+  font-size: 12px;
+  font-family: "Work Sans", sans-serif;
+  outline: 1px solid #ffffff55;
+  opacity: 0;
+  animation: faded-in 0.2s 0.15s;
+  animation-fill-mode: forwards;
+  @keyframes faded-in {
+    from { opacity: 0 }
+    to { opacity: 1 }
+  }
+`;
+
+const StyledTooltipParent = styled.div`
+  position: relative;
+`;

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

@@ -294,7 +294,7 @@ const StyledImageSelector = styled.div`
   display: flex;
   align-items: center;
   justify-content: space-between;
-  cursor: ${(props: { isExpanded: boolean, forceExpanded: boolean }) => props.forceExpanded ? '' : 'pointer'};;
+  cursor: ${(props: { isExpanded: boolean, forceExpanded: boolean }) => props.forceExpanded ? '' : 'pointer'};
   :hover {
     background: #ffffff11;
 

+ 1 - 1
dashboard/src/components/repo-selector/RepoSelector.tsx

@@ -279,7 +279,7 @@ const StyledRepoSelector = styled.div`
   display: flex;
   align-items: center;
   justify-content: space-between;
-  cursor: ${(props: { isExpanded: boolean, forceExpanded: boolean }) => props.forceExpanded ? '' : 'pointer'};;
+  cursor: ${(props: { isExpanded: boolean, forceExpanded: boolean }) => props.forceExpanded ? '' : 'pointer'};
   :hover {
     background: #ffffff11;
 

+ 8 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -153,7 +153,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     if (this.state.devOpsMode) {
       tabOptions.push(
         { label: 'Chart Overview', value: 'graph' },
-        { label: 'Search Chart', value: 'list' },
+        { label: 'Manifests', value: 'list' },
         { label: 'Raw Values', value: 'values' }
       );
     }
@@ -193,6 +193,9 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
           <ListSection
             currentChart={chart}
             components={this.state.components}
+
+            // Handle resize YAML wrapper
+            showRevisions={this.state.showRevisions}
           />
         ),
       },
@@ -257,7 +260,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
       let { tabOptions } = this.state;
       tabOptions.push(
         { label: 'Chart Overview', value: 'graph' },
-        { label: 'Search Chart', value: 'list' },
+        { label: 'Manifests', value: 'list' },
         { label: 'Raw Values', value: 'values' }
       );
       this.setState({ devOpsMode: true, tabOptions, checkTabExists: false });
@@ -367,6 +370,8 @@ const TabButton = styled.div`
   position: absolute;
   right: 0px;
   height: 30px;
+  background: linear-gradient(to right, #26282f00, #26282f 20%);
+  padding-left: 30px;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -535,7 +540,7 @@ const StyledExpandedChart = styled.div`
   z-index: 0;
   position: absolute;
   top: 25px;
-  left: 25px;;
+  left: 25px;
   border-radius: 10px;
   background: #26282f;
   box-shadow: 0 5px 12px 4px #00000033;

+ 63 - 18
dashboard/src/main/home/cluster-dashboard/expanded-chart/ListSection.tsx

@@ -1,45 +1,66 @@
 import React, { Component } from 'react';
 import styled from 'styled-components';
+import yaml from 'js-yaml';
 
 import { Context } from '../../../../shared/Context';
 import { ResourceType, ChartType } from '../../../../shared/types';
 
-import ResourceItem from './ResourceItem';
 import Loading from '../../../../components/Loading';
+import ResourceTab from '../../../../components/ResourceTab';
+import YamlEditor from '../../../../components/YamlEditor';
 
 type PropsType = {
   currentChart: ChartType,
-  components: ResourceType[]
+  components: ResourceType[],
+  showRevisions: boolean,
 };
 
 type StateType = {
-  showKindLabels: boolean
+  showKindLabels: boolean,
+  yaml: string | null,
+  wrapperHeight: number,
 };
 
 export default class ListSection extends Component<PropsType, StateType> {
   state = {
-    showKindLabels: true
+    showKindLabels: true,
+    yaml: '# Select a resource to view its manifest' as string | null,
+    wrapperHeight: 0,
+  }
+
+  wrapperRef: any = React.createRef();
+
+  componentDidMount() {
+    this.setState({ wrapperHeight: this.wrapperRef.offsetHeight });
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if ((prevProps.showRevisions !== this.props.showRevisions) && this.wrapperRef) {
+      this.setState({ wrapperHeight: this.wrapperRef.offsetHeight });
+    }
   }
 
   renderResourceList = () => {
     return this.props.components.map((resource: ResourceType, i: number) => {
+      let rawYaml = yaml.dump(resource.RawYAML);
       return (
-        <ResourceItem
-          key={i}
-          resource={resource}
-          toggleKindLabels={() => this.setState({ showKindLabels: !this.state.showKindLabels })}
-          showKindLabels={this.state.showKindLabels}
+        <ResourceTab
+          handleClick={() => this.setState({ yaml: rawYaml })}
+          selected={this.state.yaml === rawYaml}
+          kind={resource.Kind}
+          name={resource.Name}
+          isLast={i === this.props.components.length - 1}
         />
       );
     });
   }
 
-  renderContents = () => {
+  renderTabs = () => {
     if (this.props.components && this.props.components.length > 0) {
       return (
-        <ResourceList>
+        <TabWrapper>
           {this.renderResourceList()}
-        </ResourceList>
+        </TabWrapper>
       );
     }
 
@@ -49,7 +70,18 @@ export default class ListSection extends Component<PropsType, StateType> {
   render() {
     return (
       <StyledListSection>
-        {this.renderContents()}
+        {this.renderTabs()}
+        <FlexWrapper ref={element => this.wrapperRef = element}>
+          <YamlWrapper>
+            <YamlEditor
+              value={this.state.yaml}
+              onChange={(e: any) => this.setState({ yaml: e })}
+              height={this.state.wrapperHeight - 2 + 'px'}
+              border={true}
+              readOnly={true}
+            />
+          </YamlWrapper>
+        </FlexWrapper>
       </StyledListSection>
     );
   }
@@ -57,18 +89,31 @@ export default class ListSection extends Component<PropsType, StateType> {
 
 ListSection.contextType = Context;
 
-const ResourceList = styled.div`
+const YamlWrapper = styled.div`
   width: 100%;
-  overflow-y: auto;
-  padding-bottom: 150px;
+  height: 100%;
+`;
+
+const TabWrapper = styled.div`
+  min-width: 200px;
+  width: 35%;
+  margin-right: 10px;
+  border-radius: 5px;
+  overflow: hidden;
+`;
+
+const FlexWrapper = styled.div`
+  display: flex;
+  flex: 1;
+  height: 100%;
 `;
 
 const StyledListSection = styled.div`
   width: 100%;
   height: 100%;
-  background: #ffffff11;
   display: flex;
   position: relative;
-  border-radius: 5px;
   font-size: 13px;
+  border-radius: 5px;
+  overflow: hidden;
 `;

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

@@ -287,7 +287,7 @@ const RevisionHeader = styled.div`
   display: flex;
   align-items: center;
   height: 40px;
-  font-size: 14px;
+  font-size: 13px;
   width: 100%;
   padding-left: 15px;
   cursor: pointer;

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

@@ -1,17 +1,18 @@
 import React, { Component } from 'react';
 import styled from 'styled-components';
-import { kindToIcon } from '../../../../../shared/rosettaStone';
 import api from '../../../../../shared/api';
 import { Context } from '../../../../../shared/Context';
 
+import ResourceTab from '../../../../../components/ResourceTab';
+
 type PropsType = {
   controller: any,
   selectedPod: any,
   selectPod: Function,
+  isLast?: boolean,
 };
 
 type StateType = {
-  expanded: boolean,
   pods: any[],
   raw: any[],
 };
@@ -19,11 +20,51 @@ type StateType = {
 // Controller tab in log section that displays list of pods on click.
 export default class ControllerTab extends Component<PropsType, StateType> {
   state = {
-    expanded: false,
     pods: [] as any[],
     raw: [] as any[],
   }
 
+  componentDidMount() {
+    let { currentCluster, currentProject, setCurrentError } = this.context;
+    let { controller } = this.props;
+
+    let selectors = [] as string[];
+    let ml = controller?.spec?.selector?.matchLabels || controller?.spec?.selector;
+    let i = 1;
+    let selector = '';
+    for (var key in ml) {
+      selector += key + '=' + ml[key];
+      if (i != Object.keys(ml).length) {
+        selector += ',';
+      }
+      i += 1;
+    }
+    selectors.push(selector);
+    
+    api.getMatchingPods('<token>', { 
+      cluster_id: currentCluster.id,
+      service_account_id: currentCluster.service_account_id,
+      selectors,
+    }, {
+      id: currentProject.id
+    }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+        setCurrentError(JSON.stringify(err));
+        return
+      }
+      let pods = res?.data?.map((pod: any) => {
+        return {
+          namespace: pod?.metadata?.namespace, 
+          name: pod?.metadata?.name,
+          phase: pod?.status?.phase,
+        }
+      });
+      console.log(res.data);
+      this.setState({ pods, raw: res.data });
+    })
+  }
+
   getAvailability = (kind: string, c: any) => {
     switch (kind?.toLowerCase()) {
       case "deployment":
@@ -39,20 +80,6 @@ export default class ControllerTab extends Component<PropsType, StateType> {
       }
   }
 
-  renderIcon = (kind: string) => {
-
-    let icon = 'tonality';
-    if (Object.keys(kindToIcon).includes(kind)) {
-      icon = kindToIcon[kind]; 
-    }
-    
-    return (
-      <IconWrapper>
-        <i className="material-icons">{icon}</i>
-      </IconWrapper>
-    );
-  }
-
   getPodStatus = (status: any) => {
     if (status?.phase == 'Pending') {
       return 'waiting'
@@ -77,159 +104,81 @@ export default class ControllerTab extends Component<PropsType, StateType> {
     }
   }
 
-  renderExpanded = () => {
-    if (this.state.expanded) {
-      return (
-        <ExpandWrapper>
-            {
-              this.state.raw.map((pod) => {
-                let status = this.getPodStatus(pod.status)
-                return (
-                  <Tab 
-                    key={pod.metadata?.name}
-                    selected={(this.props.selectedPod?.metadata?.name === pod?.metadata?.name)}
-                    onClick={() => {this.props.selectPod(pod)}}
-                  > 
-                    {pod.metadata?.name}
-                    <Status>
-                      <StatusColor status={status} />
-                      {status}
-                    </Status>
-                  </Tab>)
-              })
-            }
-        </ExpandWrapper>
-      );
-    }
-  }
-
-  componentDidMount() {
-    let { currentCluster, currentProject, setCurrentError } = this.context;
-    let { controller } = this.props;
-
-    let selectors = [] as string[]
-    let ml = controller?.spec?.selector?.matchLabels || controller?.spec?.selector
-    let i = 1;
-    let selector = ''
-    for (var key in ml) {
-      selector += key + '=' + ml[key]
-      if (i != Object.keys(ml).length) {
-        selector += ','
-      }
-      i += 1;
-    }
-    selectors.push(selector)
-    
-    api.getMatchingPods('<token>', { 
-      cluster_id: currentCluster.id,
-      service_account_id: currentCluster.service_account_id,
-      selectors,
-    }, {
-      id: currentProject.id
-    }, (err: any, res: any) => {
-      if (err) {
-        console.log(err)
-        setCurrentError(JSON.stringify(err))
-        return
-      }
-      let pods = res?.data?.map((pod: any) => {
-        return {
-          namespace: pod?.metadata?.namespace, 
-          name: pod?.metadata?.name,
-          phase: pod?.status?.phase,
-        }
-      })
-      console.log(res.data)
-      this.setState({ pods, raw: res.data })
-    })
-  }
-
   render() {
-    let { controller } = this.props;
+    let { controller, selectedPod, isLast, selectPod } = this.props;
     let [available, total] = this.getAvailability(controller.kind, controller);
     let status = (available == total) ? 'running' : 'waiting'
     return (
-      <StyledResourceItem>
-        <ResourceHeader
-          expanded={this.state.expanded}
-          onClick={() => this.setState({ expanded: !this.state.expanded })}
-        >
-          <DropdownIcon expanded={this.state.expanded}>
-            <i className="material-icons">arrow_right</i>
-          </DropdownIcon>
-          <Info>
-          <Metadata>
-            {this.renderIcon(controller.kind)}
-            {`${controller.kind}`}
-            <ResourceName
-              showKindLabels={true}
-            >
-              {controller.metadata.name}
-            </ResourceName>
-          </Metadata>
-          <Status>
-            <StatusColor status={status} />
-            {available}/{total}
-          </Status>
-          </Info>
-        </ResourceHeader>
-        {this.renderExpanded()}
-      </StyledResourceItem>
+      <ResourceTab
+        kind={controller.kind}
+        name={controller.metadata.name}
+        status={{ label: status, available, total }}
+        isLast={isLast}
+      >
+        {
+          this.state.raw.map((pod, i) => {
+            let status = this.getPodStatus(pod.status)
+            return (
+              <Tab 
+                key={pod.metadata?.name}
+                selected={selectedPod?.metadata?.name === pod?.metadata?.name}
+                onClick={() => {selectPod(pod)}}
+              > 
+                <Gutter>
+                  <Rail />
+                  <Circle />
+                  <Rail lastTab={i === this.state.raw.length - 1} />
+                </Gutter>
+                {pod.metadata?.name}
+                <Status>
+                  <StatusColor status={status} />
+                  {status}
+                </Status>
+              </Tab>
+            );
+          })
+        }
+      </ResourceTab>
     );
   }
 }
 
 ControllerTab.contextType = Context;
 
-const StyledResourceItem = styled.div`
-  width: 100%;
+const Rail = styled.div`
+  width: 2px;
+  background: ${(props: { lastTab?: boolean }) => props.lastTab ? '' : '#52545D'};
+  height: 50%;
 `;
 
-const ExpandWrapper = styled.div`
-  overflow: hidden;
-`;
-
-const ResourceHeader = styled.div`
-  width: 100%;
-  height: 60px;
-  display: flex;
-  align-items: center;
-  color: #ffffff66;
-  padding: 8px 13px;
-  text-transform: capitalize;
-  cursor: pointer;
-  background: ${(props: { expanded: boolean }) => props.expanded ? '#ffffff11' : ''};
-  :hover {
-    background: #ffffff18;
-
-    > i {
-      background: #ffffff22;
-    }
-  }
+const Circle = styled.div`
+  min-width: 10px;
+  min-height: 2px;
+  margin-bottom: -2px;
+  margin-left: 8px;
+  background: #52545D;
 `;
 
-const Info = styled.div`
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-  align-items: center;
-  width: 100%;
+const Gutter = styled.div`
+  position: absolute;
+  top: 0px;
+  left: 10px;
   height: 100%;
-`;
-
-const Metadata = styled.div`
   display: flex;
+  flex-direction: column;
   align-items: center;
-  width: 85%;
+  justify-content: center;
+  overflow: visible;
 `;
 
 const Status = styled.div`
   display: flex;
-  font-size: 13px;
-  flex-direction: row;
+  width: 50px;
+  font-size: 12px;
   text-transform: capitalize;
+  justify-content: flex-end;
   align-items: center;
-  font-family: 'Hind Siliguri', sans-serif;
+  font-family: 'Work Sans', sans-serif;
   color: #aaaabb;
   animation: fadeIn 0.5s;
   @keyframes fadeIn {
@@ -239,75 +188,29 @@ const Status = styled.div`
 `;
 
 const StatusColor = styled.div`
-  margin-bottom: 1px;
-  margin-right: 5px;
-  width: 8px;
-  height: 8px;
+  margin-right: 7px;
+  width: 7px;
+  min-width: 7px;
+  height: 7px;
   background: ${(props: { status: string }) => (props.status === 'running' ? '#4797ff' : props.status === 'failed' ? "#ed5f85" : "#f5cb42")};
   border-radius: 20px;
 `;
 
 const Tab = styled.div`
   width: 100%;
-  height: 100%;
+  height: 50px;
+  position: relative;
   display: flex;
   align-items: center;
   justify-content: space-between;
   color: ${(props: {selected: boolean}) => props.selected ? 'white' : '#ffffff66'};
-  background: ${(props: {selected: boolean}) => props.selected ? '#ffffff18' : '##ffffff11'};
+  background: ${(props: {selected: boolean}) => props.selected ? '#ffffff18' : ''};
   font-size: 13px;
-  padding: 20px 12px 20px 45px;
+  padding: 20px 19px 20px 42px;
   text-shadow: 0px 0px 8px none;
   cursor: pointer;
   :hover {
     color: white;
     background: #ffffff18;
   }
-`;
-
-const ResourceName = styled.div`
-  color: #ffffff;
-  margin-left: ${(props: { showKindLabels: boolean }) => props.showKindLabels ? '10px' : ''};
-  text-transform: none;
-  max-width: 60%;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-
-  :hover {
-    overflow: visible;
-  }
-`;
-
-const IconWrapper = styled.div`
-  width: 25px;
-  height: 25px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-
-  > i {
-    font-size: 16px;
-    color: #ffffff;
-    margin-right: 14px;
-  }
-`;
-
-const DropdownIcon = styled.div`
-  > i {
-    margin-right: 13px;
-    font-size: 20px;
-    color: #ffffff66;
-    cursor: pointer;
-    border-radius: 20px;
-    background: ${(props: { expanded: boolean }) => props.expanded ? '#ffffff18' : ''};
-    transform: ${(props: { expanded: boolean }) => props.expanded ? 'rotate(180deg)' : ''};
-    animation: ${(props: { expanded: boolean }) => props.expanded ? 'quarterTurn 0.3s' : ''};
-    animation-fill-mode: forwards;
-
-    @keyframes quarterTurn {
-      from { transform: rotate(0deg) }
-      to { transform: rotate(90deg) }
-    }
-  }
 `;

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

@@ -72,7 +72,9 @@ export default class Logs extends Component<PropsType, StateType> {
   render() {
     return (
       <LogStream ref={this.scrollRef}>
-        {this.renderLogs()}
+        <Wrapper>
+          {this.renderLogs()}
+        </Wrapper>
       </LogStream>
     );
   }
@@ -80,16 +82,21 @@ export default class Logs extends Component<PropsType, StateType> {
 
 Logs.contextType = Context;
 
+const Wrapper = styled.div`
+  width: 100%;
+  height: 100%;
+`;
+
 const LogStream = styled.div`
   overflow: auto;
-  width: 65%;
+  display: flex;
+  flex: 1;
   float: right;
   height: 100%;
   background: #202227;
-  padding: 25px;
+  padding: 25px 30px;
   user-select: text;
   overflow: auto;
-  border-radius: 5px;
 `;
 
 const Message = styled.div`
@@ -100,4 +107,4 @@ const Message = styled.div`
   justify-content: center;
   color: #ffffff44;
   font-size: 14px;
-`
+`;

+ 9 - 8
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx

@@ -43,13 +43,14 @@ export default class StatusSection extends Component<PropsType, StateType> {
   }
 
   renderTabs = () => {
-    return this.state.controllers.map((c) => {
+    return this.state.controllers.map((c, i) => {
       return (
         <ControllerTab 
           key={c.metadata.uid} 
           selectedPod={this.state.selectedPod} 
           selectPod={this.selectPod.bind(this)}
           controller={c}
+          isLast={i === this.state.controllers.length - 1}
         />
       )
     })
@@ -116,14 +117,11 @@ export default class StatusSection extends Component<PropsType, StateType> {
 StatusSection.contextType = Context;
 
 const TabWrapper = styled.div`
-  display: flex;
-  flex-direction: column;
-  overflow: auto;
   width: 35%;
-  float: left;
-  max-height: 100%;
-  background: #ffffff11;
-`
+  min-width: 250px;
+  height: 100%;
+  overflow-y: auto;
+`;
 
 const StyledStatusSection = styled.div`
   width: 100%;
@@ -132,11 +130,14 @@ const StyledStatusSection = styled.div`
   font-size: 13px;
   padding: 0px;
   user-select: text;
+  border-radius: 5px;
+  overflow: hidden;
 `;
 
 const Wrapper = styled.div`
   width: 100%;
   height: 100%;
+  display: flex;
 `;
 
 const NoControllers = styled.div`

+ 1 - 1
dashboard/src/main/home/sidebar/ProjectSection.tsx

@@ -175,7 +175,7 @@ const Option = styled.div`
   font-size: 13px;
   align-items: center;
   padding-left: 10px;
-  cursor: ${(props: { selected: boolean, lastItem?: boolean }) => props.selected ? '' : 'pointer'};;
+  cursor: ${(props: { selected: boolean, lastItem?: boolean }) => props.selected ? '' : 'pointer'};
   padding-right: 10px;
   background: ${(props: { selected: boolean, lastItem?: boolean }) => props.selected ? '#ffffff11' : ''};
   :hover {

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

@@ -1,7 +1,7 @@
 import axios from 'axios';
 import qs from 'qs';
 
-// axios.defaults.timeout = 2500;
+axios.defaults.timeout = 8000;
 
 // Partial function that accepts a generic params type and returns an api method
 export const baseApi = <T extends {}, S = {}>(requestType: string, endpoint: ((pathParams: S) => string) | string) => {