jusrhee 5 лет назад
Родитель
Сommit
5b590dce78

+ 151 - 0
dashboard/src/components/Selector.tsx

@@ -0,0 +1,151 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+type PropsType = {
+  activeValue: string,
+  options: { value: string, label: string }[],
+  setActiveValue: (x: string) => void,
+  dropdownLabel: string
+};
+
+type StateType = {
+};
+
+export default class Selector extends Component<PropsType, StateType> {
+  state = {
+    expanded: false
+  }
+
+  renderOptionList = () => {
+    let { options, activeValue, setActiveValue } = this.props;
+    return options.map((option: { value: string, label: string }, i: number) => {
+      return (
+        <Option
+          key={i}
+          selected={option.value === activeValue}
+          onClick={() => setActiveValue(option.value)}
+        >
+          {option.label}
+        </Option>
+      );
+    });
+  }
+
+  renderDropdown = () => {
+    if (this.state.expanded) {
+      return (
+        <div>
+          <CloseOverlay onClick={() => this.setState({ expanded: false })}/>
+          <Dropdown>
+            <DropdownLabel>
+              {this.props.dropdownLabel}
+            </DropdownLabel>
+            {this.renderOptionList()}
+          </Dropdown>
+        </div>
+      )
+    }
+  }
+
+  render() {
+    let { activeValue } = this.props;
+    return (
+      <StyledSelector>
+        <MainSelector
+          onClick={() => this.setState({ expanded: !this.state.expanded })}
+          expanded={this.state.expanded}
+        >
+          <TextWrap>
+            {activeValue === '' ? 'All' : activeValue}
+          </TextWrap>
+          <i className="material-icons">arrow_drop_down</i>
+        </MainSelector>
+        {this.renderDropdown()}
+      </StyledSelector>
+    );
+  }
+}
+
+const TextWrap = styled.div`
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+`;
+
+const DropdownLabel = styled.div`
+  font-size: 13px;
+  color: #ffffff44;
+  font-weight: 500;
+  margin: 10px 13px;
+`;
+
+const Option = styled.div` 
+  width: 100%;
+  border-bottom: 1px solid #ffffff10;
+  height: 35px;
+  font-size: 13px;
+  padding-top: 9px;
+  align-items: center;
+  padding-left: 15px;
+  cursor: pointer;
+  padding-right: 10px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  background: ${(props: { selected: boolean }) => props.selected ? '#ffffff11' : ''};
+
+  :hover {
+    background: #ffffff22;
+  }
+`;
+
+const CloseOverlay = styled.div`
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: 999;
+`;
+
+const Dropdown = styled.div`
+  position: absolute;
+  right: 0;
+  top: calc(100% + 5px);
+  background: #26282f;
+  width: calc(100% + 80px);
+  max-height: 300px;
+  padding-bottom: 20px;
+  border-radius: 3px;
+  z-index: 999;
+  overflow-y: auto;
+  box-shadow: 0 8px 20px 0px #00000055;
+`;
+
+const StyledSelector = styled.div`
+  position: relative;
+`;
+
+const MainSelector = styled.div`
+  width: 150px;
+  height: 30px;
+  border: 1px solid #ffffff66;
+  font-size: 13px;
+  padding: 5px 10px;
+  padding-left: 12px;
+  border-radius: 3px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  cursor: pointer;
+  background: ${(props: { expanded: boolean }) => props.expanded ? '#ffffff33' : '#ffffff11'};
+
+  :hover {
+    background: ${(props: { expanded: boolean }) => props.expanded ? '#ffffff33' : '#ffffff22'};
+  }
+
+  > i {
+    font-size: 20px;
+    transform: ${(props: { expanded: boolean }) => props.expanded ? 'rotate(180deg)' : ''};
+  }
+`;

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

@@ -114,6 +114,9 @@ const GlobalStyle = createGlobalStyle`
     box-sizing: border-box;
     font-family: 'Work Sans', sans-serif;
   }
+  body {
+    background: #202227;
+  }
 `;
 
 const StyledMain = styled.div`
@@ -122,6 +125,6 @@ const StyledMain = styled.div`
   position: fixed;
   top: 0;
   left: 0;
-  background: #24272a;
+  background: #202227;
   color: white;
 `;

+ 43 - 23
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -5,14 +5,20 @@ import gradient from '../../../assets/gradient.jpg';
 import { Context } from '../../../shared/Context';
 
 import ChartList from './chart/ChartList';
+import NamespaceSelector from './NamespaceSelector';
 
 type PropsType = {
 };
 
 type StateType = {
+  namespace: string
 };
 
 export default class Dashboard extends Component<PropsType, StateType> {
+  state = {
+    namespace: ''
+  }
+
   render() {
     let { currentCluster } = this.context;
 
@@ -37,8 +43,22 @@ export default class Dashboard extends Component<PropsType, StateType> {
         </InfoSection>
 
         <LineBreak />
-
-        <ChartList currentCluster={currentCluster} />
+        
+        <ControlRow>
+          <Button disabled={true}>
+            <i className="material-icons">add</i> Add a Chart
+          </Button>
+          <NamespaceSelector
+            setNamespace={(namespace) => this.setState({ namespace })}
+            namespace={this.state.namespace}
+            currentCluster={currentCluster}
+          />
+        </ControlRow>
+
+        <ChartList
+          currentCluster={currentCluster}
+          namespace={this.state.namespace}
+        />
       </div>
     );
   }
@@ -46,6 +66,14 @@ export default class Dashboard extends Component<PropsType, StateType> {
 
 Dashboard.contextType = Context;
 
+const ControlRow = styled.div`
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 35px;
+  padding-left: 0px;
+`;
+
 const TopRow = styled.div`
   display: flex;
   align-items: center;
@@ -75,43 +103,35 @@ const InfoLabel = styled.div`
 const InfoSection = styled.div`
   margin-top: 20px;
   font-family: 'Work Sans', sans-serif;
-  margin-left: 7px;
+  margin-left: 0px;
   margin-bottom: 35px;
 `;
 
-const ButtonWrap = styled.div`
-  display: flex;
-  align-items: center;
-  font-size: 18px;
-  margin-top: 2px;
-  margin-bottom: 25px;
-  color: #00000020;
-`;
-
 const Button = styled.div`
-  min-width: 145px;
-  max-width: 145px;
   display: flex;
-  flex: 1;
   flex-direction: row;
   align-items: center;
   justify-content: space-between;
   font-size: 13px;
   cursor: pointer;
   font-family: 'Work Sans', sans-serif;
-  margin-left: 5px;
   border-radius: 20px;
   color: white;
-  padding: 6px 8px;
+  height: 30px;
+  padding: 0px 8px;
+  padding-bottom: 1px;
   margin-right: 10px;
-  padding-right: 13px;
+  font-weight: 500;
+  padding-right: 15px;
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
+  box-shadow: 0 5px 8px 0px #00000010;
+  cursor: not-allowed;
 
-  background: #616FEEcc;
+  background: ${(props: { disabled: boolean }) => props.disabled ? '#aaaabbee' :'#616FEEcc'};
   :hover {
-    background: #505edddd;
+    background: ${(props: { disabled: boolean }) => props.disabled ? '' : '#505edddd'};
   }
 
   > i {
@@ -122,7 +142,7 @@ const Button = styled.div`
     border-radius: 20px;
     display: flex;
     align-items: center;
-    margin-top: -1px;
+    margin-right: 8px;
     justify-content: center;
   }
 `;
@@ -196,7 +216,7 @@ const Title = styled.div`
   font-size: 20px;
   font-weight: 500;
   font-family: 'Work Sans', sans-serif;
-  margin-left: 20px;
+  margin-left: 18px;
   color: #ffffff;
   white-space: nowrap;
   overflow: hidden;
@@ -210,7 +230,7 @@ const TitleSection = styled.div`
   display: flex;
   flex-direction: row;
   align-items: center;
-  padding-left: 17px;
+  padding-left: 0px;
 
   > i {
     margin-left: 10px;

+ 87 - 0
dashboard/src/main/home/dashboard/NamespaceSelector.tsx

@@ -0,0 +1,87 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import { Context } from '../../../shared/Context';
+import api from '../../../shared/api';
+
+import Selector from '../../../components/Selector';
+
+type PropsType = {
+  setNamespace: (x: string) => void,
+  currentCluster: string,
+  namespace: string
+};
+
+type StateType = {
+  namespaceOptions: { label: string, value: string }[]
+};
+
+
+// TODO: display selected in option dropdown and actually filter!
+
+export default class NamespaceSelector extends Component<PropsType, StateType> {
+  state = {
+    namespaceOptions: [] as { label: string, value: string }[]
+  }
+
+  updateOptions = () => {
+    let { currentCluster, setCurrentError } = this.context;
+
+    api.getNamespaces('<token>', { context: currentCluster }, {}, (err: any, res: any) => {
+      if (err) {
+        setCurrentError('Could not read clusters: ' + JSON.stringify(err));
+      } else {
+        let namespaceOptions: { label: string, value: string }[] = [];
+        res.data.items.forEach((x: { metadata: { name: string }}, i: number) => {
+          namespaceOptions.push({ label: x.metadata.name, value: x.metadata.name });
+        })
+        this.setState({ namespaceOptions });
+      }
+    });
+  }
+
+  componentDidMount() {
+    this.updateOptions();
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if (prevProps !== this.props) {
+      this.updateOptions();
+    }
+  }
+
+  render() {
+    return ( 
+      <StyledNamespaceSelector>
+        <Label>
+          <i className="material-icons">filter_alt</i> Filter
+        </Label>
+        <Selector
+          activeValue={this.props.namespace}
+          setActiveValue={(namespace) => this.props.setNamespace(namespace)}
+          options={this.state.namespaceOptions}
+          dropdownLabel='Namespace:'
+        />
+      </StyledNamespaceSelector>
+    );
+  }
+}
+
+NamespaceSelector.contextType = Context;
+
+const Label = styled.div`
+  display: flex;
+  align-items: center;
+  margin-right: 12px;
+
+  > i {
+    margin-right: 8px;
+    font-size: 18px;
+  }
+`;
+
+const StyledNamespaceSelector = styled.div`
+  display: flex;
+  align-items: center;
+  font-size: 13px;
+`;

+ 3 - 2
dashboard/src/main/home/dashboard/chart/ChartList.tsx

@@ -9,7 +9,8 @@ import Chart from './Chart';
 import Loading from '../../../../components/Loading';
 
 type PropsType = {
-  currentCluster: string
+  currentCluster: string,
+  namespace: string
 };
 
 type StateType = {
@@ -28,7 +29,7 @@ export default class ChartList extends Component<PropsType, StateType> {
     
     this.setState({ loading: true });
     api.getCharts('<token>', {
-      namespace: '',
+      namespace: this.props.namespace,
       context: currentCluster,
       storage: 'secret',
       limit: 20,

+ 5 - 0
dashboard/src/shared/api.tsx

@@ -50,6 +50,10 @@ const getCharts = baseApi<{
   statusFilter: string[]
 }>('GET', '/api/charts');
 
+const getNamespaces = baseApi<{
+  context: string
+}>('GET', '/api/k8s/namespaces');
+
 // Bundle export to allow default api import (api.<method> is more readable)
 export default {
   checkAuth,
@@ -60,4 +64,5 @@ export default {
   updateUser,
   getContexts,
   getCharts,
+  getNamespaces
 }