Răsfoiți Sursa

cluster-level dashboard boilerplate

jusrhee 5 ani în urmă
părinte
comite
afa389350a

+ 0 - 0
dashboard/src/assets/grad.jpg → dashboard/src/assets/gradient.jpg


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

@@ -5,6 +5,7 @@ import ReactModal from 'react-modal';
 import { Context } from '../../shared/Context';
 
 import Sidebar from './sidebar/Sidebar';
+import Dashboard from './dashboard/Dashboard';
 import ClusterConfigModal from './modals/ClusterConfigModal';
 
 type PropsType = {
@@ -28,9 +29,7 @@ export default class Home extends Component<PropsType, StateType> {
         </ReactModal>
 
         <Sidebar logOut={this.props.logOut} />
-        <DummyDashboard>
-          🏗️🏗️🏗️🏗️🏗️
-        </DummyDashboard>
+        <Dashboard />
       </StyledHome>
     );
   }

+ 267 - 0
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -0,0 +1,267 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import gradient from '../../../assets/gradient.jpg';
+
+import { Context } from '../../../shared/Context';
+import api from '../../../shared/api';
+import { StorageType } from '../../../shared/types';
+
+class Dashboard extends Component {
+
+  componentDidMount() {
+    let { userId, setCurrentError } = this.context;
+
+    api.getCharts('<token>', {
+      user_id: userId,
+      helm: {
+        namespace: '',
+        context: 'minikube',
+        storage: 'memory',
+      },
+      filter: {
+        namespace: '',
+        limit: 20,
+        skip: 0,
+        byDate: false,
+        statusFilter: ['deployed']
+      }
+    }, {}, (err: any, res: any) => {
+      if (err) {
+        setCurrentError(JSON.stringify(err));
+      } else {
+        
+        console.log(res);
+      }
+    });
+  }
+
+  render() {
+    let { currentCluster } = this.context;
+
+    return ( 
+      <StyledDashboard>
+        <DashboardWrapper>
+        <TitleSection>
+          <ProjectIcon>
+            <ProjectImage src={gradient} />
+            <Overlay>{currentCluster && currentCluster[0].toUpperCase()}</Overlay>
+          </ProjectIcon>
+          <Title>{currentCluster}</Title>
+          <i className="material-icons">more_vert</i>
+        </TitleSection>
+
+        <InfoSection>
+          <TopRow>
+            <InfoLabel>
+              <i className="material-icons">info</i> Info
+            </InfoLabel>
+          </TopRow>
+          <Description>Some description perhaps.</Description>
+        </InfoSection>
+
+        <LineBreak />
+        </DashboardWrapper>
+      </StyledDashboard>
+    );
+  }
+}
+
+Dashboard.contextType = Context;
+export default Dashboard;
+
+const TopRow = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const Description = styled.div`
+  color: #ffffff;
+  margin-top: 13px;
+  margin-left: 2px;
+  font-size: 13px;
+`;
+
+const InfoLabel = styled.div`
+  width: 72px;
+  height: 20px;
+  display: flex;
+  align-items: center;
+  color: #7A838F;
+  font-size: 13px;
+  > i {
+    color: #8B949F;
+    font-size: 18px;
+    margin-right: 5px;
+  }
+`;
+
+const InfoSection = styled.div`
+  margin-top: 20px;
+  font-family: 'Work Sans', sans-serif;
+  margin-left: 7px;
+  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;
+  margin-right: 10px;
+  padding-right: 13px;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+
+  background: #616FEEcc;
+  :hover {
+    background: #505edddd;
+  }
+
+  > i {
+    color: white;
+    width: 18px;
+    height: 18px;
+    font-size: 12px;
+    border-radius: 20px;
+    display: flex;
+    align-items: center;
+    margin-top: -1px;
+    justify-content: center;
+  }
+`;
+
+const ButtonStack = styled(Button)`
+  min-width: 119px;
+  max-width: 119px;
+  background: #616FEEcc;
+  :hover {
+    background: #505edddd;
+  }
+`;
+
+const ButtonAlt = styled(Button)`
+  min-width: 150px;
+  max-width: 150px;
+  background: #7A838Fdd;
+
+  :hover {
+    background: #69727eee;
+  }
+`;
+
+const ConfigButtonAlt = styled(ButtonAlt)`
+  min-width: 166px;
+  max-width: 166px;
+`;
+
+const LineBreak = styled.div`
+  width: calc(100% - 180px);
+  height: 2px;
+  background: #ffffff20;
+  margin: 10px 80px 35px;
+`;
+
+const ServiceSection = styled.div`
+  padding-bottom: 150px;
+`;
+
+const Overlay = styled.div`
+  height: 100%;
+  width: 100%;
+  position: absolute;
+  background: #00000028;
+  top: 0;
+  left: 0;
+  border-radius: 5px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 24px;
+  font-weight: 500;
+  font-family: 'Work Sans', sans-serif;
+  color: white;
+`;
+
+const ProjectImage = styled.img`
+  height: 45px;
+  width: 45px;
+  border-radius: 5px;
+`;
+
+const ProjectIcon = styled.div`
+  position: relative;
+  height: 45px;
+  width: 45px;
+  border-radius: 5px;
+`;
+
+const Title = styled.div`
+  font-size: 20px;
+  font-weight: 500;
+  font-family: 'Work Sans', sans-serif;
+  margin-left: 20px;
+  color: #ffffff;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+`;
+
+const TitleSection = styled.div`
+  height: 80px;
+  margin-bottom: 10px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding-left: 17px;
+
+  > i {
+    margin-left: 10px;
+    cursor: pointer;
+    font-size 18px;
+    color: #858FAAaa;
+    padding: 5px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+    margin-bottom: -3px;
+  }
+`;
+
+const StyledDashboard = styled.div`
+  height: 100%;
+  width: 100vw;
+  padding-top: 80px;
+  overflow-y: auto;
+  display: flex;
+  flex: 1;
+  justify-content: center;
+  background: #24272a;
+  position: relative;
+`;
+
+const DashboardWrapper = styled.div`
+  width: 80%;
+  min-width: 300px;
+  padding-bottom: 120px;
+`;

+ 14 - 11
dashboard/src/main/home/sidebar/ClusterSection.tsx

@@ -17,8 +17,7 @@ type StateType = {
   configExists: boolean,
   showDrawer: boolean,
   initializedDrawer: boolean,
-  kubeContexts: KubeContextConfig[],
-  activeIndex: number,
+  kubeContexts: KubeContextConfig[]
 };
 
 export default class ClusterSection extends Component<PropsType, StateType> {
@@ -28,22 +27,27 @@ export default class ClusterSection extends Component<PropsType, StateType> {
     configExists: true,
     showDrawer: false,
     initializedDrawer: false,
-    kubeContexts: [] as KubeContextConfig[],
-    activeIndex: 0,
+    kubeContexts: [] as KubeContextConfig[]
   };
 
   updateClusters = () => {
-    let { setCurrentError, userId } = this.context;
+    let { setCurrentError, userId, setCurrentCluster } = this.context;
 
     // TODO: query with selected filter once implemented
     api.getContexts('<token>', {}, { id: userId }, (err: any, res: any) => {
       if (err) {
         setCurrentError('getContexts: ' + JSON.stringify(err));
       } else {
-
+        
         // Filter selected (temporary)
         let kubeContexts = res.data.filter((x: KubeContextConfig) => x.selected);
-        this.setState({ kubeContexts });
+        if (kubeContexts.length > 0) {
+          this.setState({ kubeContexts });
+          setCurrentCluster(kubeContexts[0].name);
+        } else {
+          this.setState({ kubeContexts: [] });
+          setCurrentCluster(null);
+        }
       }
     });
   }
@@ -77,8 +81,6 @@ export default class ClusterSection extends Component<PropsType, StateType> {
           toggleDrawer={this.toggleDrawer}
           showDrawer={this.state.showDrawer}
           kubeContexts={this.state.kubeContexts}
-          activeIndex={this.state.activeIndex}
-          setActiveIndex={(i: number): void => this.setState({ activeIndex: i })}
         />
       );
     }
@@ -90,14 +92,15 @@ export default class ClusterSection extends Component<PropsType, StateType> {
   }
 
   renderContents = (): JSX.Element => {
-    let { kubeContexts, activeIndex, showDrawer } = this.state;
+    let { kubeContexts, showDrawer } = this.state;
+    let { currentCluster } = this.context;
 
     if (kubeContexts.length > 0) {
       return (
         <ClusterSelector showDrawer={showDrawer}>
           <LinkWrapper>
             <ClusterIcon><i className="material-icons">polymer</i></ClusterIcon>
-            <ClusterName>{kubeContexts[activeIndex].name}</ClusterName>
+            <ClusterName>{currentCluster}</ClusterName>
           </LinkWrapper>
           <DrawerButton onClick={this.toggleDrawer}>
             <BgAccent src={drawerBg} />

+ 5 - 5
dashboard/src/main/home/sidebar/Drawer.tsx

@@ -9,8 +9,6 @@ type PropsType = {
   toggleDrawer: () => void,
   showDrawer: boolean,
   kubeContexts: KubeContextConfig[],
-  activeIndex: number,
-  setActiveIndex: (i: number) => void,
   updateClusters: () => void
 };
 
@@ -20,7 +18,9 @@ type StateType = {
 export default class Drawer extends Component<PropsType, StateType> {
 
   renderClusterList = (): JSX.Element[] | JSX.Element => {
-    let { kubeContexts, activeIndex, setActiveIndex } = this.props;
+    let { kubeContexts } = this.props;
+    let { currentCluster, setCurrentCluster } = this.context;
+
     if (kubeContexts.length > 0) {
       return kubeContexts.map((kubeContext: KubeContextConfig, i: number) => {
         /*
@@ -31,8 +31,8 @@ export default class Drawer extends Component<PropsType, StateType> {
         return (
           <ClusterOption
             key={i}
-            active={i === activeIndex}
-            onClick={() => setActiveIndex(i)}
+            active={kubeContext.name === currentCluster}
+            onClick={() => setCurrentCluster(kubeContext.name)}
           >
             <ClusterIcon><i className="material-icons">polymer</i></ClusterIcon>
             <ClusterName>{kubeContext.name}</ClusterName>

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

@@ -1,6 +1,6 @@
 import React, { Component } from 'react';
 import styled from 'styled-components';
-import gradient from '../../../assets/grad.jpg';
+import gradient from '../../../assets/gradient.jpg';
 
 import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';

+ 20 - 1
dashboard/src/shared/api.tsx

@@ -1,10 +1,12 @@
 import axios from 'axios';
 import { baseApi } from './baseApi';
 
+import { StorageType } from './types';
+
 /**
  * Generic api call format
  * @param {string} token - Bearer token.
- * @param {Object} params - Query params.
+ * @param {Object} params - Body params.
  * @param {Object} pathParams - Path params.
  * @param {(err: Object, res: Object) => void} callback - Callback function.
  */
@@ -38,6 +40,22 @@ const getContexts = baseApi<{}, { id: number }>('GET', pathParams => {
   return `/api/users/${pathParams.id}/contexts`;
 });
 
+const getCharts = baseApi<{
+  user_id: number,
+  helm: {
+    namespace: string,
+    context: string,
+    storage: string
+  },
+  filter: {
+    namespace: string,
+    limit: number,
+    skip: number,
+    byDate: boolean,
+    statusFilter: string[]
+  }
+}>('GET', '/api/charts');
+
 // Bundle export to allow default api import (api.<method> is more readable)
 export default {
   checkAuth,
@@ -47,4 +65,5 @@ export default {
   getUser,
   updateUser,
   getContexts,
+  getCharts,
 }

+ 6 - 0
dashboard/src/shared/types.tsx

@@ -4,4 +4,10 @@ export interface KubeContextConfig {
   selected?: boolean,
   server: string,
   user: string
+}
+
+export enum StorageType {
+  Secret = 'secret',
+  ConfigMap = 'configmap',
+  Memory = 'memory'
 }