Explorar el Código

project-based dashboard reorganization (broken repo queries)

jusrhee hace 5 años
padre
commit
403a2826ac
Se han modificado 33 ficheros con 787 adiciones y 722 borrados
  1. 6 0
      dashboard/src/assets/filter.svg
  2. 3 0
      dashboard/src/assets/home.svg
  3. 6 1
      dashboard/src/components/repo-selector/RepoSelector.tsx
  4. 44 14
      dashboard/src/main/home/Home.tsx
  5. 0 92
      dashboard/src/main/home/Toolbar.tsx
  6. 327 0
      dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx
  7. 15 8
      dashboard/src/main/home/cluster-dashboard/NamespaceSelector.tsx
  8. 0 0
      dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx
  9. 10 6
      dashboard/src/main/home/cluster-dashboard/chart/ChartList.tsx
  10. 18 6
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx
  11. 0 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/GraphSection.tsx
  12. 0 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ListSection.tsx
  13. 0 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/LogSection.tsx
  14. 0 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ResourceItem.tsx
  15. 12 7
      dashboard/src/main/home/cluster-dashboard/expanded-chart/RevisionSection.tsx
  16. 0 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx
  17. 9 5
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ValuesYaml.tsx
  18. 0 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/Edge.tsx
  19. 0 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/GraphDisplay.tsx
  20. 0 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/InfoPanel.tsx
  21. 0 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/Node.tsx
  22. 0 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/SelectRegion.tsx
  23. 16 102
      dashboard/src/main/home/dashboard/Dashboard.tsx
  24. 0 379
      dashboard/src/main/home/modals/ClusterConfigModal.tsx
  25. 165 0
      dashboard/src/main/home/modals/ClusterInstructionsModal.tsx
  26. 10 13
      dashboard/src/main/home/modals/LaunchTemplateModal.tsx
  27. 39 23
      dashboard/src/main/home/sidebar/ClusterSection.tsx
  28. 12 13
      dashboard/src/main/home/sidebar/Drawer.tsx
  29. 21 16
      dashboard/src/main/home/sidebar/ProjectSection.tsx
  30. 10 6
      dashboard/src/main/home/sidebar/Sidebar.tsx
  31. 6 4
      dashboard/src/shared/Context.tsx
  32. 44 23
      dashboard/src/shared/api.tsx
  33. 14 4
      dashboard/src/shared/types.tsx

+ 6 - 0
dashboard/src/assets/filter.svg

@@ -0,0 +1,6 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.4" d="M10.0833 15.958H3.50777C2.67555 15.958 2 16.6217 2 17.4393C2 18.2558 2.67555 18.9206 3.50777 18.9206H10.0833C10.9155 18.9206 11.5911 18.2558 11.5911 17.4393C11.5911 16.6217 10.9155 15.958 10.0833 15.958Z" fill="white"/>
+<path opacity="0.4" d="M22 6.37856C22 5.56203 21.3244 4.89832 20.4933 4.89832H13.9178C13.0856 4.89832 12.4101 5.56203 12.4101 6.37856C12.4101 7.19618 13.0856 7.85989 13.9178 7.85989H20.4933C21.3244 7.85989 22 7.19618 22 6.37856Z" fill="white"/>
+<path d="M8.87774 6.37856C8.87774 8.24523 7.33886 9.75821 5.43887 9.75821C3.53999 9.75821 2 8.24523 2 6.37856C2 4.51298 3.53999 3 5.43887 3C7.33886 3 8.87774 4.51298 8.87774 6.37856Z" fill="white"/>
+<path d="M22 17.3992C22 19.2648 20.4611 20.7778 18.5611 20.7778C16.6623 20.7778 15.1223 19.2648 15.1223 17.3992C15.1223 15.5325 16.6623 14.0196 18.5611 14.0196C20.4611 14.0196 22 15.5325 22 17.3992Z" fill="white"/>
+</svg>

+ 3 - 0
dashboard/src/assets/home.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9.14373 20.7821V17.7152C9.14372 16.9381 9.77567 16.3067 10.5584 16.3018H13.4326C14.2189 16.3018 14.8563 16.9346 14.8563 17.7152V20.7732C14.8562 21.4473 15.404 21.9951 16.0829 22H18.0438C18.9596 22.0023 19.8388 21.6428 20.4872 21.0007C21.1356 20.3586 21.5 19.4868 21.5 18.5775V9.86585C21.5 9.13139 21.1721 8.43471 20.6046 7.9635L13.943 2.67427C12.7785 1.74912 11.1154 1.77901 9.98539 2.74538L3.46701 7.9635C2.87274 8.42082 2.51755 9.11956 2.5 9.86585V18.5686C2.5 20.4637 4.04738 22 5.95617 22H7.87229C8.19917 22.0023 8.51349 21.8751 8.74547 21.6464C8.97746 21.4178 9.10793 21.1067 9.10792 20.7821H9.14373Z" fill="white"/>
+</svg>

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

@@ -5,6 +5,7 @@ import info from '../../assets/info.svg';
 
 import api from '../../shared/api';
 import { RepoType } from '../../shared/types';
+import { Context } from '../../shared/Context';
 
 import Loading from '../../components/Loading';
 import BranchList from './BranchList';
@@ -36,9 +37,11 @@ export default class RepoSelector extends Component<PropsType, StateType> {
   }
 
   componentDidMount() {
+    let { currentProject, currentCluster } = this.context;
 
     // Get repos
-    api.getRepos('<token>', {}, {}, (err: any, res: any) => {
+    api.getRepos('<token>', {
+    }, { id: currentProject.id }, (err: any, res: any) => {
       if (err) {
         this.setState({ loading: false, error: true });
       } else {
@@ -172,6 +175,8 @@ export default class RepoSelector extends Component<PropsType, StateType> {
   }
 }
 
+RepoSelector.contextType = Context;
+
 const SelectedBranch = styled.div`
   color: #ffffff55;
   margin-left: 10px;

+ 44 - 14
dashboard/src/main/home/Home.tsx

@@ -7,11 +7,12 @@ import api from '../../shared/api';
 
 import Sidebar from './sidebar/Sidebar';
 import Dashboard from './dashboard/Dashboard';
+import ClusterDashboard from './cluster-dashboard/ClusterDashboard';
 import Loading from '../../components/Loading';
 import Templates from './templates/Templates';
-import ClusterConfigModal from './modals/ClusterConfigModal';
 import LaunchTemplateModal from './modals/LaunchTemplateModal';
 import CreateProjectModal from './modals/CreateProjectModal';
+import ClusterInstructionsModal from './modals/ClusterInstructionsModal';
 
 type PropsType = {
   logOut: () => void
@@ -27,7 +28,7 @@ export default class Home extends Component<PropsType, StateType> {
   state = {
     forceSidebar: true,
     showWelcome: false,
-    currentView: 'dashboard'
+    currentView: 'cluster-dashboard'
   }
 
   componentDidMount() {
@@ -43,10 +44,11 @@ export default class Home extends Component<PropsType, StateType> {
     });
   }
 
+  // TODO: move into ClusterDashboard
   renderDashboard = () => {
     let { currentCluster, setCurrentModal } = this.context;
 
-    if (currentCluster === '' || this.state.showWelcome) {
+    if (currentCluster === {} || this.state.showWelcome) {
       return (
         <DashboardWrapper>
           <Placeholder>
@@ -67,7 +69,7 @@ export default class Home extends Component<PropsType, StateType> {
 
     return (
       <DashboardWrapper>
-        <Dashboard
+        <ClusterDashboard
           currentCluster={currentCluster}
           setSidebar={(x: boolean) => this.setState({ forceSidebar: x })}
         />
@@ -76,29 +78,29 @@ export default class Home extends Component<PropsType, StateType> {
   }
 
   renderContents = () => {
-    if (this.state.currentView === 'dashboard') {
+    if (this.state.currentView === 'cluster-dashboard') {
       return (
         <StyledDashboard>
           {this.renderDashboard()}
         </StyledDashboard>
       );
+    } else if (this.state.currentView === 'dashboard') {
+      return (
+        <StyledDashboard>
+          <DashboardWrapper>
+            <Dashboard />
+          </DashboardWrapper>
+        </StyledDashboard>
+      );
     }
 
-    return <Templates />
+    return <Templates />;
   }
 
   render() {
     let { currentModal, setCurrentModal, currentProject } = this.context;
     return (
       <StyledHome>
-        <ReactModal
-          isOpen={currentModal === 'ClusterConfigModal'}
-          onRequestClose={() => setCurrentModal(null, null)}
-          style={MediumModalStyles}
-          ariaHideApp={false}
-        >
-          <ClusterConfigModal />
-        </ReactModal>
         <ReactModal
           isOpen={currentModal === 'LaunchTemplateModal'}
           onRequestClose={() => setCurrentModal(null, null)}
@@ -115,6 +117,15 @@ export default class Home extends Component<PropsType, StateType> {
         >
           <CreateProjectModal />
         </ReactModal>
+        <ReactModal
+          isOpen={currentModal === 'ClusterInstructionsModal'}
+          onRequestClose={() => setCurrentModal(null, null)}
+          style={TallModalStyles}
+          ariaHideApp={false}
+        >
+          <ClusterInstructionsModal />
+        </ReactModal>
+
 
         <Sidebar
           logOut={this.props.logOut}
@@ -170,6 +181,25 @@ const ProjectModalStyles = {
   },
 };
 
+const TallModalStyles = {
+  overlay: {
+    backgroundColor: 'rgba(0,0,0,0.6)',
+    zIndex: 2,
+  },
+  content: {
+    borderRadius: '7px',
+    border: 0,
+    width: '760px',
+    maxWidth: '80vw',
+    margin: '0 auto',
+    height: '650px',
+    top: 'calc(50% - 325px)',
+    backgroundColor: '#202227',
+    animation: 'floatInModal 0.5s 0s',
+    overflow: 'visible',
+  },
+};
+
 const StyledDashboard = styled.div`
   height: 100%;
   width: 100vw;

+ 0 - 92
dashboard/src/main/home/Toolbar.tsx

@@ -1,92 +0,0 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
-import ReactModal from 'react-modal';
-
-import { Context } from '../../shared/Context';
-
-import ClusterConfigModal from './modals/ClusterConfigModal';
-
-type PropsType = {
-  logOut: () => void
-};
-
-type StateType = {
-};
-
-export default class Home extends Component<PropsType, StateType> {
-  render() {
-    return (
-      <StyledHome>
-        <ReactModal
-          isOpen={this.context.currentModal === 'ClusterConfigModal'}
-          onRequestClose={() => this.context.setCurrentModal(null)}
-          style={MediumModalStyles}
-          ariaHideApp={false}
-        >
-          <ClusterConfigModal />
-        </ReactModal>
-
-        <DummyDashboard>
-          🏗️🏗️🏗️🏗️🏗️
-        </DummyDashboard>
-      </StyledHome>
-    );
-  }
-}
-
-Home.contextType = Context;
-
-const MediumModalStyles = {
-  overlay: {
-    backgroundColor: 'rgba(0,0,0,0.6)',
-    zIndex: 2,
-  },
-  content: {
-    borderRadius: '7px',
-    border: 0,
-    width: '760px',
-    maxWidth: '80vw',
-    margin: '0 auto',
-    height: '575px',
-    top: 'calc(50% - 289px)',
-    backgroundColor: '#24272a',
-    animation: 'floatInModal 0.5s 0s',
-    overflow: 'visible',
-  },
-};
-
-const DummyDashboard = styled.div`
-  height: 100%;
-  width: 100vw;
-  font-family: 'Work Sans', sans-serif;
-  overflow-y: auto;
-  display: flex;
-  letter-spacing: 10px;
-  flex: 1;
-  justify-content: center;
-  padding-bottom: 30px;
-  align-items: center;
-  background: ${props => props.theme.bg};
-  position: relative;
-`;
-
-const StyledHome = styled.div`
-  width: 100vw;
-  height: 100vh;
-  position: fixed;
-  top: 0;
-  left: 0;
-  margin: 0;
-  user-select: none;
-  display: flex;
-  justify-content: center;
-
-  @keyframes floatInModal {
-    from {
-      opacity: 0; transform: translateY(30px);
-    }
-    to {
-      opacity: 1; transform: translateY(0px);
-    }
-  }
-`;

+ 327 - 0
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -0,0 +1,327 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import gradient from '../../../assets/gradient.jpg';
+
+import { Context } from '../../../shared/Context';
+import { ChartType, StorageType, Cluster } from '../../../shared/types';
+import api from '../../../shared/api';
+
+import ChartList from './chart/ChartList';
+import NamespaceSelector from './NamespaceSelector';
+import ExpandedChart from './expanded-chart/ExpandedChart';
+
+type PropsType = {
+  currentCluster: Cluster,
+  setSidebar: (x: boolean) => void
+};
+
+type StateType = {
+  namespace: string,
+  currentChart: ChartType | null
+};
+
+export default class ClusterDashboard extends Component<PropsType, StateType> {
+  state = {
+    namespace: '',
+    currentChart: null as (ChartType | null)
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+
+    // Reset namespace filter and close expanded chart on cluster change
+    if (prevProps.currentCluster !== this.props.currentCluster) {
+      this.setState({ namespace: '', currentChart: null });
+    }
+  }
+
+  // Allows rollback to update the top-level chart
+  refreshChart = () => {
+    let { currentProject } = this.context;
+    let { currentCluster } = this.props;
+    api.getChart('<token>', {
+      namespace: this.state.namespace,
+      cluster_id: currentCluster.id,
+      service_account_id: currentCluster.service_account_id,
+      storage: StorageType.Secret
+    }, {
+      name: this.state.currentChart.name,
+      revision: 0,
+      id: currentProject.id
+    }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+      } else {
+        this.setState({ currentChart: res.data });
+      }
+    });
+  }
+
+  renderDashboardIcon = () => {
+    if (false) {
+      let { currentCluster } = this.props;
+      return (
+        <DashboardIcon>
+          <DashboardImage src={gradient} />
+          <Overlay>{currentCluster && currentCluster.name[0].toUpperCase()}</Overlay>
+        </DashboardIcon>
+      );
+    }
+
+    return (
+      <DashboardIcon>
+        <i className="material-icons">device_hub</i>
+      </DashboardIcon>
+    );
+  }
+
+  renderContents = () => {
+    let { currentCluster, setSidebar } = this.props;
+
+    if (this.state.currentChart) {
+      return (
+        <ExpandedChart
+          currentChart={this.state.currentChart}
+          refreshChart={this.refreshChart}
+          setCurrentChart={(x: ChartType | null) => this.setState({ currentChart: x })}
+          setSidebar={setSidebar}
+        />
+      );
+    }
+
+    return (
+      <div>
+        <TitleSection>
+          {this.renderDashboardIcon()}
+          <Title>{currentCluster.name}</Title>
+          <i className="material-icons">more_vert</i>
+        </TitleSection>
+
+        <InfoSection>
+          <TopRow>
+            <InfoLabel>
+              <i className="material-icons">info</i> Info
+            </InfoLabel>
+          </TopRow>
+          <Description>Porter dashboard for {currentCluster.name}.</Description>
+        </InfoSection>
+
+        <LineBreak />
+        
+        <ControlRow>
+          <Button disabled={true}>
+            <i className="material-icons">add</i> Deploy a Chart
+          </Button>
+          <NamespaceSelector
+            setNamespace={(namespace) => this.setState({ namespace })}
+            namespace={this.state.namespace}
+          />
+        </ControlRow>
+
+        <ChartList
+          currentCluster={currentCluster}
+          namespace={this.state.namespace}
+          setCurrentChart={(x: ChartType) => this.setState({ currentChart: x })}
+        />
+      </div>
+    );
+  }
+
+  render() {
+    return (
+      <div>
+        {this.renderContents()}
+      </div>
+    );
+  }
+}
+
+ClusterDashboard.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;
+`;
+
+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: 0px;
+  margin-bottom: 35px;
+`;
+
+const Button = styled.div`
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 13px;
+  cursor: pointer;
+  font-family: 'Work Sans', sans-serif;
+  border-radius: 20px;
+  color: white;
+  height: 30px;
+  padding: 0px 8px;
+  padding-bottom: 1px;
+  margin-right: 10px;
+  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: ${(props: { disabled: boolean }) => props.disabled ? '#aaaabbee' :'#616FEEcc'};
+  :hover {
+    background: ${(props: { disabled: boolean }) => props.disabled ? '' : '#505edddd'};
+  }
+
+  > i {
+    color: white;
+    width: 18px;
+    height: 18px;
+    font-size: 12px;
+    border-radius: 20px;
+    display: flex;
+    align-items: center;
+    margin-right: 5px;
+    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 DashboardImage = styled.img`
+  height: 45px;
+  width: 45px;
+  border-radius: 5px;
+`;
+
+const DashboardIcon = styled.div`
+  position: relative;
+  height: 45px;
+  width: 45px;
+  border-radius: 5px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #676C7C;
+  border: 2px solid #8e94aa;
+
+  > i {
+    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: not-allowed;
+    font-size 18px;
+    color: #858FAAaa;
+    padding: 5px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+    margin-bottom: -3px;
+  }
+`;

+ 15 - 8
dashboard/src/main/home/dashboard/NamespaceSelector.tsx → dashboard/src/main/home/cluster-dashboard/NamespaceSelector.tsx

@@ -8,7 +8,6 @@ import Selector from '../../../components/Selector';
 
 type PropsType = {
   setNamespace: (x: string) => void,
-  currentCluster: string,
   namespace: string
 };
 
@@ -16,22 +15,25 @@ type StateType = {
   namespaceOptions: { label: string, value: string }[]
 };
 
-
-// TODO: display selected in option dropdown and actually filter!
-
+// TODO: fix update to unmounted component 
 export default class NamespaceSelector extends Component<PropsType, StateType> {
+  _isMounted = false;
+
   state = {
     namespaceOptions: [] as { label: string, value: string }[]
   }
 
   updateOptions = () => {
-    let { currentCluster, setCurrentError } = this.context;
+    let { currentCluster, currentProject } = this.context;
 
-    api.getNamespaces('<token>', { context: currentCluster }, {}, (err: any, res: any) => {
-      if (err) {
+    api.getNamespaces('<token>', {
+      cluster_id: currentCluster.id,
+      service_account_id: currentCluster.service_account_id
+    }, { id: currentProject.id }, (err: any, res: any) => {
+      if (err && this._isMounted) {
         // setCurrentError('Could not read clusters: ' + JSON.stringify(err));
         this.setState({ namespaceOptions: [{ label: 'All', value: '' }] });
-      } else {
+      } else if (this._isMounted) {
         let namespaceOptions: { label: string, value: string }[] = [{ label: 'All', value: '' }];
         res.data.items.forEach((x: { metadata: { name: string }}, i: number) => {
           namespaceOptions.push({ label: x.metadata.name, value: x.metadata.name });
@@ -42,6 +44,7 @@ export default class NamespaceSelector extends Component<PropsType, StateType> {
   }
 
   componentDidMount() {
+    this._isMounted = true;
     this.updateOptions();
   }
 
@@ -51,6 +54,10 @@ export default class NamespaceSelector extends Component<PropsType, StateType> {
     }
   }
 
+  componentWillUnmount() {
+    this._isMounted = false;
+  }
+
   render() {
     return ( 
       <StyledNamespaceSelector>

+ 0 - 0
dashboard/src/main/home/dashboard/chart/Chart.tsx → dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx


+ 10 - 6
dashboard/src/main/home/dashboard/chart/ChartList.tsx → dashboard/src/main/home/cluster-dashboard/chart/ChartList.tsx

@@ -3,13 +3,13 @@ import styled from 'styled-components';
 
 import { Context } from '../../../../shared/Context';
 import api from '../../../../shared/api';
-import { ChartType, StorageType } from '../../../../shared/types';
+import { ChartType, StorageType, Cluster } from '../../../../shared/types';
 
 import Chart from './Chart';
 import Loading from '../../../../components/Loading';
 
 type PropsType = {
-  currentCluster: string,
+  currentCluster: Cluster,
   namespace: string,
   setCurrentChart: (c: ChartType) => void
 };
@@ -28,7 +28,7 @@ export default class ChartList extends Component<PropsType, StateType> {
   }
 
   updateCharts = () => {
-    let { currentCluster } = this.context;
+    let { currentCluster, currentProject } = this.context;
 
     this.setState({ loading: true });
     setTimeout(() => {
@@ -37,16 +37,20 @@ export default class ChartList extends Component<PropsType, StateType> {
       }
     }, 3000);
 
+    console.log(currentCluster.id, currentCluster.service_account_id);
+
     api.getCharts('<token>', {
       namespace: this.props.namespace,
-      context: currentCluster,
+      cluster_id: currentCluster.id,
+      service_account_id: currentCluster.service_account_id,
       storage: StorageType.Secret,
       limit: 20,
       skip: 0,
       byDate: false,
       statusFilter: ['deployed']
-    }, {}, (err: any, res: any) => {
-      if (err) {
+    }, { id: currentProject.id }, (err: any, res: any) => {
+        if (err) {
+        console.log(err)
         // setCurrentError(JSON.stringify(err));
         this.setState({ loading: false, error: true });
       } else {

+ 18 - 6
dashboard/src/main/home/dashboard/expanded-chart/ExpandedChart.tsx → dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -150,14 +150,19 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
   }
 
   updateResources = () => {
-    let { currentCluster } = this.context;
+    let { currentCluster, currentProject } = this.context;
     let { currentChart } = this.props;
 
     api.getChartComponents('<token>', {
       namespace: currentChart.namespace,
-      context: currentCluster,
+      cluster_id: currentCluster.id,
+      service_account_id: currentCluster.service_account_id,
       storage: StorageType.Secret
-    }, { name: currentChart.name, revision: currentChart.version }, (err: any, res: any) => {
+    }, {
+      id: currentProject.id,
+      name: currentChart.name,
+      revision: currentChart.version
+    }, (err: any, res: any) => {
       if (err) {
         console.log(err)
       } else {
@@ -177,15 +182,20 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
   }
 
   setRevisionPreview = (oldChart: ChartType) => {
-    let { currentCluster } = this.context;
+    let { currentCluster, currentProject } = this.context;
     this.setState({ revisionPreview: oldChart });
 
     if (oldChart) {
       api.getChartComponents('<token>', {
         namespace: oldChart.namespace,
-        context: currentCluster,
+        cluster_id: currentCluster.id,
+        service_account_id: currentCluster.service_account_id,
         storage: StorageType.Secret
-      }, { name: oldChart.name, revision: oldChart.version }, (err: any, res: any) => {
+      }, {
+        id: currentProject.id,
+        name: oldChart.name,
+        revision: oldChart.version
+      }, (err: any, res: any) => {
         if (err) {
           console.log(err)
         } else {
@@ -199,6 +209,8 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
       } else if (this.state.currentTab === 'detailed-logs') {
         this.setState({ currentTab: 'graph' });
       }
+    } else {
+      this.updateResources();
     }
   }
 

+ 0 - 0
dashboard/src/main/home/dashboard/expanded-chart/GraphSection.tsx → dashboard/src/main/home/cluster-dashboard/expanded-chart/GraphSection.tsx


+ 0 - 0
dashboard/src/main/home/dashboard/expanded-chart/ListSection.tsx → dashboard/src/main/home/cluster-dashboard/expanded-chart/ListSection.tsx


+ 0 - 0
dashboard/src/main/home/dashboard/expanded-chart/LogSection.tsx → dashboard/src/main/home/cluster-dashboard/expanded-chart/LogSection.tsx


+ 0 - 0
dashboard/src/main/home/dashboard/expanded-chart/ResourceItem.tsx → dashboard/src/main/home/cluster-dashboard/expanded-chart/ResourceItem.tsx


+ 12 - 7
dashboard/src/main/home/dashboard/expanded-chart/RevisionSection.tsx → dashboard/src/main/home/cluster-dashboard/expanded-chart/RevisionSection.tsx

@@ -32,12 +32,13 @@ export default class RevisionSection extends Component<PropsType, StateType> {
 
   refreshHistory = () => {
     let { chart } = this.props;
-
+    let { currentCluster, currentProject } = this.context;
     api.getRevisions('<token>', {
       namespace: chart.namespace,
-      context: this.context.currentCluster,
+      cluster_id: currentCluster.id,
+      service_account_id: currentCluster.service_account_id,
       storage: StorageType.Secret
-    }, { name: chart.name }, (err: any, res: any) => {
+    }, { id: currentProject.id, name: chart.name }, (err: any, res: any) => {
       if (err) {
         console.log(err)
       } else {
@@ -66,26 +67,30 @@ export default class RevisionSection extends Component<PropsType, StateType> {
   }
 
   handleRollback = () => {
-    let { setCurrentError, currentCluster } = this.context;
+    let { setCurrentError, currentCluster, currentProject } = this.context;
 
     let revisionNumber = this.state.rollbackRevision;
     this.setState({ loading: true, rollbackRevision: null });
 
     api.rollbackChart('<token>', {
       namespace: this.props.chart.namespace,
-      context: currentCluster,
       storage: StorageType.Secret,
       revision: revisionNumber
     }, {
-      name: this.props.chart.name
+      id: currentProject.id,
+      name: this.props.chart.name,
+      cluster_id: currentCluster.id,
+      service_account_id: currentCluster.service_account_id,
     }, (err: any, res: any) => {
       if (err) {
-        setCurrentError(err.response.data.errors[0]);
+        console.log(err);
+        setCurrentError(err.response.data);
         this.setState({ loading: false });
       } else {
         this.setState({ loading: false });
         this.props.refreshChart();
         this.refreshHistory();
+        this.props.setRevisionPreview(null);
       }
     });
   }

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


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

@@ -45,18 +45,22 @@ export default class ValuesYaml extends Component<PropsType, StateType> {
   }
 
   handleSaveValues = () => {
-    let { currentCluster, setCurrentError } = this.context;
+    let { currentCluster, setCurrentError, currentProject } = this.context;
     this.setState({ saveValuesStatus: 'loading' });
 
     api.upgradeChartValues('<token>', {
       namespace: this.props.currentChart.namespace,
-      context: currentCluster,
       storage: StorageType.Secret,
       values: this.state.values
-    }, { name: this.props.currentChart.name }, (err: any, res: any) => {
+    }, {
+      id: currentProject.id, 
+      name: this.props.currentChart.name,
+      cluster_id: currentCluster.id,
+      service_account_id: currentCluster.service_account_id,
+    }, (err: any, res: any) => {
       if (err) {
-        setCurrentError(err.response.data.errors[0]);
-        this.setState({ saveValuesStatus: 'error ' });
+        setCurrentError(err.response.data);
+        this.setState({ saveValuesStatus: 'error' });
       } else {
         this.setState({ saveValuesStatus: 'successful' });
         this.props.refreshChart();

+ 0 - 0
dashboard/src/main/home/dashboard/expanded-chart/graph/Edge.tsx → dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/Edge.tsx


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


+ 0 - 0
dashboard/src/main/home/dashboard/expanded-chart/graph/InfoPanel.tsx → dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/InfoPanel.tsx


+ 0 - 0
dashboard/src/main/home/dashboard/expanded-chart/graph/Node.tsx → dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/Node.tsx


+ 0 - 0
dashboard/src/main/home/dashboard/expanded-chart/graph/SelectRegion.tsx → dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/SelectRegion.tsx


+ 16 - 102
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -6,87 +6,33 @@ import { Context } from '../../../shared/Context';
 import { ChartType, StorageType } from '../../../shared/types';
 import api from '../../../shared/api';
 
-import ChartList from './chart/ChartList';
-import NamespaceSelector from './NamespaceSelector';
-import ExpandedChart from './expanded-chart/ExpandedChart';
-
 type PropsType = {
-  currentCluster: string,
-  setSidebar: (x: boolean) => void
 };
 
 type StateType = {
-  namespace: string,
-  currentChart: ChartType | null
 };
 
 export default class Dashboard extends Component<PropsType, StateType> {
   state = {
-    namespace: '',
-    currentChart: null as (ChartType | null)
-  }
-
-  componentDidUpdate(prevProps: PropsType) {
-
-    // Reset namespace filter and close expanded chart on cluster change
-    if (prevProps.currentCluster !== this.props.currentCluster) {
-      this.setState({ namespace: '', currentChart: null });
-    }
-  }
-
-  // Allows rollback to update the top-level chart
-  refreshChart = () => {
-    let { currentCluster } = this.props;
-    api.getChart('<token>', {
-      namespace: this.state.namespace,
-      context: currentCluster,
-      storage: StorageType.Secret
-    }, { name: this.state.currentChart.name, revision: 0 }, (err: any, res: any) => {
-      if (err) {
-        console.log(err);
-      } else {
-        this.setState({ currentChart: res.data });
-      }
-    });
   }
 
   renderDashboardIcon = () => {
-    if (false) {
-      let { currentCluster } = this.props;
-      return (
-        <DashboardIcon>
-          <DashboardImage src={gradient} />
-          <Overlay>{currentCluster && currentCluster[0].toUpperCase()}</Overlay>
-        </DashboardIcon>
-      );
-    }
-
+    let { currentProject } = this.context;
     return (
       <DashboardIcon>
-        <i className="material-icons">device_hub</i>
+        <DashboardImage src={gradient} />
+        <Overlay>{currentProject && currentProject.name[0].toUpperCase()}</Overlay>
       </DashboardIcon>
     );
   }
 
   renderContents = () => {
-    let { currentCluster, setSidebar } = this.props;
-
-    if (this.state.currentChart) {
-      return (
-        <ExpandedChart
-          currentChart={this.state.currentChart}
-          refreshChart={this.refreshChart}
-          setCurrentChart={(x: ChartType | null) => this.setState({ currentChart: x })}
-          setSidebar={setSidebar}
-        />
-      );
-    }
-
+    let { currentProject } = this.context;
     return (
       <div>
         <TitleSection>
           {this.renderDashboardIcon()}
-          <Title>{currentCluster}</Title>
+          <Title>{currentProject && currentProject.name}</Title>
           <i className="material-icons">more_vert</i>
         </TitleSection>
 
@@ -96,27 +42,14 @@ export default class Dashboard extends Component<PropsType, StateType> {
               <i className="material-icons">info</i> Info
             </InfoLabel>
           </TopRow>
-          <Description>Porter dashboard for {currentCluster}.</Description>
+          <Description>Porter dashboard for {currentProject && currentProject.name}.</Description>
         </InfoSection>
 
         <LineBreak />
-        
-        <ControlRow>
-          <Button disabled={true}>
-            <i className="material-icons">add</i> Deploy a Chart
-          </Button>
-          <NamespaceSelector
-            setNamespace={(namespace) => this.setState({ namespace })}
-            namespace={this.state.namespace}
-            currentCluster={currentCluster}
-          />
-        </ControlRow>
 
-        <ChartList
-          currentCluster={currentCluster}
-          namespace={this.state.namespace}
-          setCurrentChart={(x: ChartType) => this.setState({ currentChart: x })}
-        />
+        <Placeholder>
+          🚧 Pipelines under construction.
+        </Placeholder>
       </div>
     );
   }
@@ -132,12 +65,13 @@ 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 Placeholder = styled.div`
+  width: 100%;
+  margin-top: 200px;
+  color: #aaaabb;
+  text-align: center;
+  font-size: 13px;
+  font-family: 'Work Sans', sans-serif;
 `;
 
 const TopRow = styled.div`
@@ -213,15 +147,6 @@ const Button = styled.div`
   }
 `;
 
-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;
@@ -232,11 +157,6 @@ const ButtonAlt = styled(Button)`
   }
 `;
 
-const ConfigButtonAlt = styled(ButtonAlt)`
-  min-width: 166px;
-  max-width: 166px;
-`;
-
 const LineBreak = styled.div`
   width: calc(100% - 180px);
   height: 2px;
@@ -244,10 +164,6 @@ const LineBreak = styled.div`
   margin: 10px 80px 35px;
 `;
 
-const ServiceSection = styled.div`
-  padding-bottom: 150px;
-`;
-
 const Overlay = styled.div`
   height: 100%;
   width: 100%;
@@ -279,8 +195,6 @@ const DashboardIcon = styled.div`
   display: flex;
   align-items: center;
   justify-content: center;
-  background: #676C7C;
-  border: 2px solid #8e94aa;
 
   > i {
     font-size: 22px;

+ 0 - 379
dashboard/src/main/home/modals/ClusterConfigModal.tsx

@@ -1,379 +0,0 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
-import close from '../../../assets/close.png';
-
-import api from '../../../shared/api';
-import { Context } from '../../../shared/Context';
-import { KubeContextConfig } from '../../../shared/types';
-
-import YamlEditor from '../../../components/YamlEditor';
-import SaveButton from '../../../components/SaveButton';
-import TabSelector from '../../../components/TabSelector';
-
-type PropsType = {
-};
-
-type StateType = {
-  currentTab: string,
-  kubeContexts: KubeContextConfig[],
-  rawKubeconfig: string,
-  saveKubeconfigStatus: string | null,
-  saveSelectedStatus: string | null
-};
-
-const tabOptions = [
-  { label: 'Raw Kubeconfig', value: 'kubeconfig' },
-  { label: 'Select Clusters', value: 'select' }
-];
-
-export default class ClusterConfigModal extends Component<PropsType, StateType> {
-  state = {
-    currentTab: 'kubeconfig',
-    kubeContexts: [] as KubeContextConfig[],
-    rawKubeconfig: '# If you are using certificate files, include those explicitly',
-    saveKubeconfigStatus: null as (string | null),
-    saveSelectedStatus: null as (string | null),
-  };
-  
-  updateChecklist = () => {
-    let { setCurrentError, user } = this.context;
-
-    // Parse kubeconfig to retrieve all possible clusters
-    api.getContexts('<token>', {}, { id: user.userId }, (err: any, res: any) => {
-      if (err) {
-        // setCurrentError(JSON.stringify(err));
-      } else {
-        this.setState({ kubeContexts: res.data });
-      }
-    });
-  }
-
-  componentDidMount() {
-    let { setCurrentError, user, currentModalData } = this.context;
-
-    if (currentModalData && currentModalData.currentTab) {
-      this.setState({ currentTab: 'select' });
-    }
-
-    api.getUser('<token>', {}, { id: user.userId }, (err: any, res: any) => {
-      if (err) {
-        // setCurrentError(JSON.stringify(err));
-      } else if (res.data.rawKubeConfig !== '') {
-        this.setState({ rawKubeconfig: res.data.rawKubeConfig });
-      }
-    });
-
-    this.updateChecklist();
-  }
-
-  toggleCluster = (i: number): void => {
-    let newKubeContexts = this.state.kubeContexts;
-    newKubeContexts[i].selected = !newKubeContexts[i].selected;
-    this.setState({ kubeContexts: newKubeContexts });
-  };
-
-  renderClusterList = (): JSX.Element[] | JSX.Element => {
-    let { kubeContexts } = this.state;
-
-    if (kubeContexts && kubeContexts.length > 0) {
-      return kubeContexts.map((kubeContext: KubeContextConfig, i) => {
-        return (
-          <Row key={i} onClick={() => this.toggleCluster(i)}>
-            <Checkbox checked={kubeContext.selected}>
-              <i className="material-icons">done</i>
-            </Checkbox>
-            {kubeContext.name}
-          </Row>
-        );
-      })
-    }
-
-    return (
-      <Placeholder>
-        You need to 
-        <LinkText onClick={() => this.setState({ currentTab: 'kubeconfig'})}>
-          supply a kubeconfig
-        </LinkText>
-        first
-      </Placeholder>
-    );
-  };
-
-  handleSaveKubeconfig = () => {
-    let { rawKubeconfig } = this.state;
-    let { user } = this.context;
-
-    this.setState({ saveKubeconfigStatus: 'loading' });
-    api.updateUser(
-      '<token>',
-      { rawKubeConfig: rawKubeconfig },
-      { id: user.userId },
-      (err: any, res: any) => {
-        if (err) {
-          this.setState({ saveKubeconfigStatus: 'error' });
-        } else {
-          this.setState({ 
-            rawKubeconfig: res.data.rawKubeConfig,
-            saveKubeconfigStatus: 'successful'
-          });
-
-          this.updateChecklist();
-          this.context.currentModalData.updateClusters();
-        }
-      }
-    );
-  }
-
-  handleSaveSelected = () => {
-    let { kubeContexts } = this.state;
-    let { user } = this.context;
-
-    this.setState({ saveSelectedStatus: 'loading' });
-    let allowedContexts: string[] = [];
-    kubeContexts.forEach((x, i) => {
-      if (x.selected) {
-        allowedContexts.push(x.name);
-      }
-    });
-    
-    api.updateUser(
-      '<token>',
-      { allowedContexts },
-      { id: user.userId },
-      (err: any, res: any) => {
-        if (err) {
-          this.setState({ saveSelectedStatus: 'error' });
-        } else {
-          this.setState({ saveSelectedStatus: 'successful' });
-          this.updateChecklist();
-          this.context.currentModalData.updateClusters();
-        }
-      }
-    );
-  }
-  
-  renderTabContents = (): JSX.Element => {
-    if (this.state.currentTab === 'kubeconfig') {
-      return (
-        <div>
-          <Subtitle>Copy and paste your kubeconfig below</Subtitle>
-          <YamlEditor 
-            value={this.state.rawKubeconfig}
-            onChange={(e: any) => this.setState({ rawKubeconfig: e })}
-            height='327px'
-            border={true}
-          />
-          <UploadButton>
-            <i className="material-icons">cloud_upload</i> Upload Kubeconfig
-          </UploadButton>
-          <SaveButton
-            text='Save Kubeconfig'
-            onClick={this.handleSaveKubeconfig}
-            status={this.state.saveKubeconfigStatus}
-          />
-        </div>
-      )
-    }
-
-    return (
-      <div>
-        <Subtitle>Select the contexts you want Porter to use</Subtitle>
-        <ClusterList>
-          {this.renderClusterList()}
-        </ClusterList>
-        <SaveButton
-          text='Save Selected'
-          disabled={this.state.kubeContexts.length === 0}
-          onClick={this.handleSaveSelected}
-          status={this.state.saveSelectedStatus}
-        />
-      </div>
-    )
-  };
-
-  render() {
-    return (
-      <StyledClusterConfigModal>
-        <CloseButton onClick={() => {
-          this.context.setCurrentModal(null, null);
-        }}>
-          <CloseButtonImg src={close} />
-        </CloseButton>
-
-        <ModalTitle>Manage Clusters</ModalTitle>
-        <TabSelector
-          currentTab={this.state.currentTab}
-          options={tabOptions}
-          setCurrentTab={(value: string) => this.setState({ currentTab: value })}
-        />
-        {this.renderTabContents()}
-      </StyledClusterConfigModal>
-    );
-  }
-}
-
-ClusterConfigModal.contextType = Context;
-
-const UploadButton = styled.button`
-  display: flex;
-  align-items: center;
-  position: absolute;
-  bottom: 25px;
-  left: 30px;
-  height: 40px;
-  font-size: 13px;
-  font-weight: 500;
-  font-family: 'Work Sans', sans-serif;
-  color: white;
-  padding: 6px 20px 7px 20px;
-  text-align: left;
-  border: 0;
-  border-radius: 5px;
-  background: #ffffff11;
-  box-shadow: 0 2px 5px 0 #00000030;
-  cursor: not-allowed;
-  user-select: none;
-  :focus { outline: 0 }
-  :hover {
-    background: #ffffff22;
-  }
-
-  > i {
-    font-size: 18px;
-    margin-right: 12px;
-  }
-`;
-
-const Checkbox = styled.div`
-  width: 16px;
-  height: 16px;
-  border: 1px solid #ffffff44;
-  margin: 1px 15px 0px 12px;
-  border-radius: 3px;
-  background: ${(props: { checked: boolean }) => props.checked ? '#ffffff22' : ''};
-  display: flex;
-  align-items: center;
-  justify-content: center;
-
-  > i {
-    font-size: 12px;
-    padding-left: 0px;
-    display: ${(props: { checked: boolean }) => props.checked ? '' : 'none'};
-  }
-`;
-
-const Row = styled.div`
-  width: 100%;
-  height: 40px;
-  border-bottom: 1px solid #ffffff22;
-  display: flex;
-  align-items: center;
-  color: #ffffff;
-  font-size: 13px;
-  font-family: 'Work Sans', sans-serif;
-  cursor: pointer;
-  
-  :hover {
-    background: #ffffff11;
-  }
-`;
-
-const LinkText = styled.span`
-  font-weight: 500;
-  text-decoration: underline;
-  color: #949effcc;
-  cursor: pointer;
-  margin: 0px 5px;
-`;
-
-const Placeholder = styled.div`
-  color: #ffffff44;
-  font-family: 'Work Sans', sans-serif;
-  font-size: 13px;
-  width: 100%;
-  height: 100%;
-  display: flex;
-  margin-top: -13px;
-  align-items: center;
-  justify-content: center;
-  user-select: none;
-`;
-
-const ClusterList = styled.div`
-  width: 100%;
-  height: 327px;
-  border-radius: 5px;
-  background: #ffffff06;
-  border: 1px solid #ffffff22;
-`;
-
-const Subtitle = styled.div`
-  padding: 17px 2px 25px;
-  font-family: 'Work Sans', sans-serif;
-  font-size: 13px;
-  color: #aaaabb;
-  margin-top: 8px;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const ModalTitle = styled.div`
-  margin: 0px 2px 13px;
-  display: flex;
-  flex: 1;
-  font-family: 'Assistant';
-  font-size: 18px;
-  color: #ffffff;
-  font-weight: 700;
-  align-items: center;
-  position: relative;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const Header = styled.div`
-  display: inline-block;
-  width: 100%;
-  font-size: 14px;
-  color: #7A838Faa;
-  font-family: 'Work Sans', sans-serif;
-`;
-
-const Plus = styled.span`
-  margin-right: 10px;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  z-index: 1;
-  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 StyledClusterConfigModal= styled.div`
-  width: 100%;
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  padding: 25px 30px;
-  overflow: hidden;
-  border-radius: 6px;
-  background: #202227;
-`;

+ 165 - 0
dashboard/src/main/home/modals/ClusterInstructionsModal.tsx

@@ -0,0 +1,165 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import close from '../../../assets/close.png';
+import TabSelector from '../../../components/TabSelector';
+
+import { Context } from '../../../shared/Context';
+
+type PropsType = {
+};
+
+type StateType = {
+  currentTab: string,
+};
+
+const tabOptions = [
+  { label: 'MacOS', value: 'mac' }
+];
+
+export default class ClusterInstructionsModal extends Component<PropsType, StateType> {
+  state = {
+    currentTab: 'mac'
+  }
+ 
+  render() {
+    return (
+      <StyledClusterInstructionsModal>
+        <CloseButton onClick={() => {
+          this.context.setCurrentModal(null, null);
+        }}>
+          <CloseButtonImg src={close} />
+        </CloseButton>
+
+        <ModalTitle>Connecting to an Existing Cluster</ModalTitle>
+
+        <TabSelector
+          options={tabOptions}
+          currentTab={this.state.currentTab}
+          setCurrentTab={(value: string) => this.setState({ currentTab: value })}
+        />
+
+        <Placeholder>
+          1. Run the following command to retrieve the latest binary:
+          <Code>
+            &#123;<br />
+            name=$(curl -s https://api.github.com/repos/porter-dev/porter/releases/latest | grep "browser_download_url.*_Darwin_x86_64\.zip" | cut -d ":" -f 2,3 | tr -d \")<br />
+            name=$(basename $name)<br />
+            curl -L https://github.com/porter-dev/porter/releases/latest/download/$name --output $name<br />
+            unzip -a $name<br />
+            rm $name<br />
+            &#125;
+          </Code>
+          2. Move the file into your bin:
+          <Code>
+            chmod +x ./porter<br />
+            sudo mv ./porter /usr/local/bin/porter
+          </Code>
+          3. Log in to the Porter CLI:
+          <Code>
+            porter auth login
+          </Code>
+          4. Configure the Porter CLI and link your current context:
+          <Code>
+            porter config set-project {this.context.currentProject.id}<br/>
+            porter config set-host {location.protocol + '//' + location.host}<br/>
+            porter connect kubeconfig
+          </Code>
+        </Placeholder>
+        
+      </StyledClusterInstructionsModal>
+    );
+  }
+}
+
+ClusterInstructionsModal.contextType = Context;
+
+const Code = styled.div`
+  background: #181B21;
+  padding: 10px 15px;
+  border: 1px solid #ffffff44;
+  border-radius: 5px;
+  margin: 10px 0px 15px;
+  color: #ffffff;
+  font-size: 13px;
+  user-select: text;
+  font-family: monospace;
+`;
+
+const A = styled.a`
+  color: #ffffff;
+  text-decoration: underline;
+  cursor: ${(props: { disabled?: boolean }) => props.disabled ? 'not-allowed' : 'pointer'};
+`;
+
+const Placeholder = styled.div`
+  color: #aaaabb;
+  font-size: 13px;
+  margin-left: 0px;
+  margin-top: 25px;
+  user-select: none;
+`;
+
+const Bold = styled.div`
+  font-weight: bold;
+  font-size: 20px;
+`;
+
+const Subtitle = styled.div`
+  padding: 17px 0px 25px;
+  font-family: 'Work Sans', sans-serif;
+  font-size: 13px;
+  color: #aaaabb;
+  margin-top: 3px;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+`;
+
+const ModalTitle = styled.div`
+  margin: 0px 0px 13px;
+  display: flex;
+  flex: 1;
+  font-family: 'Assistant';
+  font-size: 18px;
+  color: #ffffff;
+  user-select: none;
+  font-weight: 700;
+  align-items: center;
+  position: relative;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+`;
+
+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 StyledClusterInstructionsModal= styled.div`
+  width: 100%;
+  position: absolute;
+  left: 0;
+  top: 0;
+  height: 100%;
+  padding: 25px 32px;
+  overflow: hidden;
+  border-radius: 6px;
+  background: #202227;
+`;

+ 10 - 13
dashboard/src/main/home/modals/LaunchTemplateModal.tsx

@@ -4,7 +4,7 @@ import close from '../../../assets/close.png';
 
 import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
-import { KubeContextConfig, RepoType } from '../../../shared/types';
+import { Cluster, RepoType } from '../../../shared/types';
 
 import SaveButton from '../../../components/SaveButton';
 import Selector from '../../../components/Selector';
@@ -16,7 +16,7 @@ type PropsType = {
 
 type StateType = {
   currentView: string,
-  contextOptions: { label: string, value: string }[],
+  clusterOptions: { label: string, value: string }[],
   selectedCluster: string,
   selectedRepo: RepoType | null,
   selectedBranch: string,
@@ -26,27 +26,24 @@ type StateType = {
 export default class LaunchTemplateModal extends Component<PropsType, StateType> {
   state = {
     currentView: 'repo',
-    contextOptions: [] as { label: string, value: string }[],
-    selectedCluster: this.context.currentCluster,
+    clusterOptions: [] as { label: string, value: string }[],
+    selectedCluster: this.context.currentCluster.name,
     selectedRepo: null as RepoType | null,
     selectedBranch: '',
     subdirectory: '',
   };
   
   componentDidMount() {
-    let { setCurrentError, user } = this.context;
+    let { currentProject } = this.context;
 
     // TODO: query with selected filter once implemented
-    api.getContexts('<token>', {}, { id: user.userId }, (err: any, res: any) => {
+    api.getClusters('<token>', {}, { id: currentProject.id }, (err: any, res: any) => {
       if (err) {
         // console.log(err)
       } else if (res.data) {
-
-        // Filter selected (temporary)
-        let kubeContexts = res.data.filter((x: KubeContextConfig) => x.selected);
-        let contextOptions = kubeContexts.map((x: KubeContextConfig) => { return { label: x.name, value: x.name } });
-        if (kubeContexts.length > 0) {
-          this.setState({ contextOptions });
+        let clusterOptions = res.data.map((x: Cluster) => { return { label: x.name, value: x.name } });
+        if (res.data.length > 0) {
+          this.setState({ clusterOptions });
         }
       }
     });
@@ -129,7 +126,7 @@ export default class LaunchTemplateModal extends Component<PropsType, StateType>
             <Selector
               activeValue={this.state.selectedCluster}
               setActiveValue={(cluster: string) => this.setState({ selectedCluster: cluster })}
-              options={this.state.contextOptions}
+              options={this.state.clusterOptions}
               width='250px'
               dropdownWidth='335px'
               closeOverlay={true}

+ 39 - 23
dashboard/src/main/home/sidebar/ClusterSection.tsx

@@ -4,7 +4,7 @@ import drawerBg from '../../../assets/drawer-bg.png';
 
 import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
-import { KubeContextConfig } from '../../../shared/types';
+import { Cluster } from '../../../shared/types';
 
 import Drawer from './Drawer';
 
@@ -19,7 +19,10 @@ type PropsType = {
 type StateType = {
   showDrawer: boolean,
   initializedDrawer: boolean,
-  kubeContexts: KubeContextConfig[]
+  clusters: Cluster[],
+
+  // Track last project id for refreshing clusters on project change
+  prevProjectId: number
 };
 
 export default class ClusterSection extends Component<PropsType, StateType> {
@@ -28,14 +31,15 @@ export default class ClusterSection extends Component<PropsType, StateType> {
   state = {
     showDrawer: false,
     initializedDrawer: false,
-    kubeContexts: [] as KubeContextConfig[]
+    clusters: [] as Cluster[],
+    prevProjectId: this.context.currentProject.id
   };
 
   updateClusters = () => {
-    let { setCurrentError, user, setCurrentCluster } = this.context;
+    let { currentProject, setCurrentCluster } = this.context;
 
     // TODO: query with selected filter once implemented
-    api.getContexts('<token>', {}, { id: user.userId }, (err: any, res: any) => {
+    api.getClusters('<token>', {}, { id: currentProject.id }, (err: any, res: any) => {
       if (err) {
 
         // Assume intializing if no contexts
@@ -45,15 +49,13 @@ export default class ClusterSection extends Component<PropsType, StateType> {
         
         // TODO: handle uninitialized kubeconfig
         if (res.data) {
-
-          // Filter selected (temporary)
-          let kubeContexts = res.data.filter((x: KubeContextConfig) => x.selected);
-          if (kubeContexts.length > 0) {
-            this.setState({ kubeContexts });
-            setCurrentCluster(kubeContexts[0].name);
+          let clusters = res.data;
+          if (clusters.length > 0) {
+            this.setState({ clusters });
+            setCurrentCluster(clusters[0]);
           } else {
-            this.setState({ kubeContexts: [] });
-            setCurrentCluster('');
+            this.setState({ clusters: [] });
+            setCurrentCluster({});
           }
         }
       }
@@ -67,6 +69,13 @@ export default class ClusterSection extends Component<PropsType, StateType> {
   // Need to override showDrawer when the sidebar is closed
   componentDidUpdate(prevProps: PropsType) {
     if (prevProps !== this.props) {
+
+      // Refresh clusters on project change 
+      if (this.state.prevProjectId !== this.context.currentProject.id) {
+        this.updateClusters();
+        this.setState({ prevProjectId: this.context.currentProject.id });
+      }
+
       if (this.props.forceCloseDrawer && this.state.showDrawer) {
         this.setState({ showDrawer: false });
         this.props.releaseDrawer();
@@ -85,10 +94,9 @@ export default class ClusterSection extends Component<PropsType, StateType> {
     if (this.state.initializedDrawer) {
       return (
         <Drawer
-          updateClusters={this.updateClusters}
           toggleDrawer={this.toggleDrawer}
           showDrawer={this.state.showDrawer}
-          kubeContexts={this.state.kubeContexts}
+          clusters={this.state.clusters}
         />
       );
     }
@@ -99,15 +107,15 @@ export default class ClusterSection extends Component<PropsType, StateType> {
   }
 
   renderContents = (): JSX.Element => {
-    let { kubeContexts, showDrawer } = this.state;
+    let { clusters, showDrawer } = this.state;
     let { currentCluster } = this.context;
 
-    if (kubeContexts.length > 0) {
+    if (clusters.length > 0) {
       return (
         <ClusterSelector isSelected={this.props.isSelected}>
-          <LinkWrapper onClick={() => this.props.setCurrentView('dashboard')}>
+          <LinkWrapper onClick={() => this.props.setCurrentView('cluster-dashboard')}>
             <ClusterIcon><i className="material-icons">device_hub</i></ClusterIcon>
-            <ClusterName>{currentCluster}</ClusterName>
+            <ClusterName>{currentCluster && currentCluster.name}</ClusterName>
           </LinkWrapper>
           <DrawerButton onClick={this.toggleDrawer}>
             <BgAccent src={drawerBg} />
@@ -117,13 +125,21 @@ export default class ClusterSection extends Component<PropsType, StateType> {
           </DrawerButton>
         </ClusterSelector>
       );
+    } else if (false) {
+      return (
+        <InitializeButton onClick={this.showClusterConfigModal}>
+          <Plus>+</Plus> Add a Cluster
+        </InitializeButton>
+      );
     }
 
     return (
-      <InitializeButton onClick={this.showClusterConfigModal}>
+      <InitializeButton
+        onClick={() => this.context.setCurrentModal('ClusterInstructionsModal', {})}
+      >
         <Plus>+</Plus> Add a Cluster
       </InitializeButton>
-    );
+    )
   };
 
   render() {
@@ -233,11 +249,11 @@ const DropdownIcon = styled.span`
 
 const ClusterIcon = styled.div`
   > i {
-    font-size: 18px;
+    font-size: 16px;
     display: flex;
     align-items: center;
     margin-bottom: 0px;
-    margin-left: 15px;
+    margin-left: 17px;
     margin-right: 10px;
   }
 `;

+ 12 - 13
dashboard/src/main/home/sidebar/Drawer.tsx

@@ -3,13 +3,12 @@ import styled from 'styled-components';
 import close from '../../../assets/close.png';
 
 import { Context } from '../../../shared/Context';
-import { KubeContextConfig } from '../../../shared/types';
+import { Cluster } from '../../../shared/types';
 
 type PropsType = {
   toggleDrawer: () => void,
   showDrawer: boolean,
-  kubeContexts: KubeContextConfig[],
-  updateClusters: () => void
+  clusters: Cluster[]
 };
 
 type StateType = {
@@ -18,11 +17,11 @@ type StateType = {
 export default class Drawer extends Component<PropsType, StateType> {
 
   renderClusterList = (): JSX.Element[] | JSX.Element => {
-    let { kubeContexts } = this.props;
+    let { clusters } = this.props;
     let { currentCluster, setCurrentCluster } = this.context;
 
-    if (kubeContexts.length > 0) {
-      return kubeContexts.map((kubeContext: KubeContextConfig, i: number) => {
+    if (clusters.length > 0) {
+      return clusters.map((cluster: Cluster, i: number) => {
         /*
         let active = this.context.activeProject &&
           this.context.activeProject.namespace == val.namespace; 
@@ -31,11 +30,11 @@ export default class Drawer extends Component<PropsType, StateType> {
         return (
           <ClusterOption
             key={i}
-            active={kubeContext.name === currentCluster}
-            onClick={() => setCurrentCluster(kubeContext.name)}
+            active={cluster.name === currentCluster.name}
+            onClick={() => setCurrentCluster(cluster)}
           >
             <ClusterIcon><i className="material-icons">device_hub</i></ClusterIcon>
-            <ClusterName>{kubeContext.name}</ClusterName>
+            <ClusterName>{cluster.name}</ClusterName>
           </ClusterOption>
         );
       });
@@ -64,9 +63,9 @@ export default class Drawer extends Component<PropsType, StateType> {
           {this.renderClusterList()}
 
           <InitializeButton onClick={() => {
-            this.context.setCurrentModal('ClusterConfigModal', { updateClusters: this.props.updateClusters });
+            this.context.setCurrentModal('ClusterInstructionsModal', {});
           }}>
-            <Plus>+</Plus> Manage Clusters
+            <Plus>+</Plus> Add a Cluster
           </InitializeButton>
         </StyledDrawer>
       </div>
@@ -173,11 +172,11 @@ const CloseButtonImg = styled.img`
 
 const ClusterIcon = styled.div`
   > i {
-    font-size: 18px;
+    font-size: 16px;
     display: flex;
     align-items: center;
     margin-bottom: 0px;
-    margin-left: 15px;
+    margin-left: 17px;
     margin-right: 10px;
   }
 `;

+ 21 - 16
dashboard/src/main/home/sidebar/ProjectSection.tsx

@@ -4,10 +4,7 @@ import gradient from '../../../assets/gradient.jpg';
 
 import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
-import { KubeContextConfig } from '../../../shared/types';
-
-import Selector from '../../../components/Selector';
-import Drawer from './Drawer';
+import { ProjectType } from '../../../shared/types';
 
 type PropsType = {
   setWelcome?: (x: boolean) => void,
@@ -15,7 +12,7 @@ type PropsType = {
 };
 
 type StateType = {
-  projects: any[],
+  projects: ProjectType[],
   expanded: boolean
 };
 
@@ -28,19 +25,20 @@ const options = [
 
 export default class ProjectSection extends Component<PropsType, StateType> {
   state = {
-    projects: [] as any[],
+    projects: [] as ProjectType[],
     expanded: false,
   };
 
   updateProjects = () => {
-    console.log('rip')
     let { user } = this.context;
     api.getProjects('<token>', {}, { id: user.userId }, (err: any, res: any) => {
       if (err) {
         console.log(err)
       } else if (res.data) {
         this.setState({ projects: res.data });
-        this.context.setCurrentProject(res.data[0].name);
+        if (res.data.length > 0) {
+          this.context.setCurrentProject(res.data[0]);
+        }
       }
     });
   }
@@ -57,12 +55,12 @@ export default class ProjectSection extends Component<PropsType, StateType> {
   }
 
   renderOptionList = () => {
-    return this.state.projects.map((project: any, i: number) => {
+    return this.state.projects.map((project: ProjectType, i: number) => {
       return (
         <Option
           key={i}
           selected={project.name === this.context.currentProject}
-          onClick={() => this.context.setCurrentProject(project.name)}
+          onClick={() => this.context.setCurrentProject(project)}
         >
           <ProjectIcon>
             <ProjectImage src={gradient} />
@@ -106,9 +104,9 @@ export default class ProjectSection extends Component<PropsType, StateType> {
           >
             <ProjectIcon>
               <ProjectImage src={gradient} />
-              <Letter>{currentProject[0].toUpperCase()}</Letter>
+              <Letter>{currentProject.name[0].toUpperCase()}</Letter>
             </ProjectIcon>
-            <ProjectName>{currentProject}</ProjectName>
+            <ProjectName>{currentProject.name}</ProjectName>
             <i className="material-icons">arrow_drop_down</i>
           </MainSelector>
           {this.renderDropdown()}
@@ -174,11 +172,18 @@ const Option = styled.div`
   font-size: 13px;
   align-items: center;
   padding-left: 10px;
-  cursor: pointer;
+  cursor: ${(props: { selected: boolean, lastItem?: boolean }) => props.selected ? '' : 'pointer'};;
   padding-right: 10px;
   background: ${(props: { selected: boolean, lastItem?: boolean }) => props.selected ? '#ffffff11' : ''};
   :hover {
-    background: #ffffff22;
+    background: ${(props: { selected: boolean, lastItem?: boolean }) => props.selected ? '' : '#ffffff22'};
+  }
+
+  > i {
+    font-size: 18px;
+    margin-right: 12px;
+    margin-left: 5px;
+    color: #ffffff44;
   }
 `;
 
@@ -262,7 +267,7 @@ const MainSelector = styled.div`
   padding-left: 20px;
   :hover {
     > i {
-      background: #ffffff11;
+      background: #ffffff22;
     }
   }
 
@@ -274,6 +279,6 @@ const MainSelector = styled.div`
     align-items: center;
     justify-content: center;
     border-radius: 20px;
-    background: ${(props: { expanded: boolean }) => props.expanded ? '#ffffff11' : ''};
+    background: ${(props: { expanded: boolean }) => props.expanded ? '#ffffff22' : ''};
   }
 `;

+ 10 - 6
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -4,6 +4,7 @@ import gradient from '../../../assets/gradient.jpg';
 import category from '../../../assets/category.svg';
 import pipelines from '../../../assets/pipelines.svg';
 import integrations from '../../../assets/integrations.svg';
+import filter from '../../../assets/filter.svg';
 
 import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
@@ -108,17 +109,20 @@ export default class Sidebar extends Component<PropsType, StateType> {
       return (
         <div>
           <SidebarLabel>Home</SidebarLabel>
+          <NavButton
+            onClick={() => this.props.setCurrentView('dashboard')}
+            selected={this.props.currentView === 'dashboard'}
+          >
+            <img src={category} />
+            Dashboard
+          </NavButton>
           <NavButton
             onClick={() => this.props.setCurrentView('templates')}
             selected={this.props.currentView === 'templates'}
           >
-            <img src={category} />
+            <img src={filter} />
             Templates
           </NavButton>
-          <NavButton disabled={true}>
-            <img src={pipelines} />
-            Pipelines
-          </NavButton>
           <NavButton disabled={true}>
             <img src={integrations} />
             Integrations
@@ -132,7 +136,7 @@ export default class Sidebar extends Component<PropsType, StateType> {
             releaseDrawer={() => this.setState({ forceCloseDrawer: false })}
             setWelcome={this.props.setWelcome}
             setCurrentView={this.props.setCurrentView}
-            isSelected={this.props.currentView === 'dashboard'}
+            isSelected={this.props.currentView === 'cluster-dashboard'}
           />
         </div>
       );

+ 6 - 4
dashboard/src/shared/Context.tsx

@@ -1,5 +1,7 @@
 import React, { Component } from 'react';
 
+import { ProjectType, Cluster } from '../shared/types';
+
 type PropsType = {
 }
 
@@ -33,12 +35,12 @@ class ContextProvider extends Component {
     setCurrentError: (currentError: string) => {
       this.setState({ currentError });
     },
-    currentCluster: null as string | null,
-    setCurrentCluster: (currentCluster: string) => {
+    currentCluster: null as Cluster | null,
+    setCurrentCluster: (currentCluster: Cluster) => {
       this.setState({ currentCluster });
     },
-    currentProject: null as string | null,
-    setCurrentProject: (currentProject: string) => {
+    currentProject: null as ProjectType | null,
+    setCurrentProject: (currentProject: ProjectType) => {
       this.setState({ currentProject });
     },
     user: null as any,

+ 44 - 23
dashboard/src/shared/api.tsx

@@ -36,69 +36,90 @@ const updateUser = baseApi<{
   return `/api/users/${pathParams.id}`;
 });
 
-const getContexts = baseApi<{}, { id: number }>('GET', pathParams => {
-  return `/api/users/${pathParams.id}/contexts`;
+const getClusters = baseApi<{}, { id: number }>('GET', pathParams => {
+  return `/api/projects/${pathParams.id}/clusters`;
 });
 
 const getCharts = baseApi<{
   namespace: string,
-  context: string,
+  cluster_id: number,
+  service_account_id: number,
   storage: StorageType,
   limit: number,
   skip: number,
   byDate: boolean,
   statusFilter: string[]
-}>('GET', '/api/releases');
+}, { id: number }>('GET', pathParams => {
+  return `/api/projects/${pathParams.id}/releases`;
+});
 
 const getChart = baseApi<{
   namespace: string,
-  context: string,
+  cluster_id: number,
+  service_account_id: number,
   storage: StorageType
-}, { name: string, revision: number }>('GET', pathParams => {
-  return `/api/releases/${pathParams.name}/${pathParams.revision}`;
+}, { id: number, name: string, revision: number }>('GET', pathParams => {
+  return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}`;
 });
 
 const getChartComponents = baseApi<{
   namespace: string,
-  context: string,
+  cluster_id: number,
+  service_account_id: number,
   storage: StorageType
-}, { name: string, revision: number }>('GET', pathParams => {
-  return `/api/releases/${pathParams.name}/${pathParams.revision}/components`;
+}, { id: number, name: string, revision: number }>('GET', pathParams => {
+  return `/api/projects/${pathParams.id}/releases/${pathParams.name}/${pathParams.revision}/components`;
 });
 
 const getNamespaces = baseApi<{
-  context: string
-}>('GET', '/api/k8s/namespaces');
+  cluster_id: number,
+  service_account_id: number,
+}, { id: number }>('GET', pathParams => {
+  return `/api/projects/${pathParams.id}/k8s/namespaces`;
+});
 
 const getRevisions = baseApi<{
   namespace: string,
-  context: string,
+  cluster_id: number,
+  service_account_id: number,
   storage: StorageType
-}, { name: string }>('GET', pathParams => {
-  return `/api/releases/${pathParams.name}/history`;
+}, { id: number, name: string }>('GET', pathParams => {
+  return `/api/projects/${pathParams.id}/releases/${pathParams.name}/history`;
 });
 
 const rollbackChart = baseApi<{
   namespace: string,
-  context: string,
   storage: StorageType,
   revision: number
-}, { name: string }>('POST', pathParams => {
-  return `/api/releases/${pathParams.name}/rollback`;
+}, {
+  id: number,
+  name: string,
+  cluster_id: number,
+  service_account_id: number,
+  }>('POST', pathParams => {
+  let { id, name, cluster_id, service_account_id } = pathParams;
+  return `/api/projects/${id}/releases/${name}/rollback?cluster_id=${cluster_id}&service_account_id=${service_account_id}`;
 });
 
 const upgradeChartValues = baseApi<{
   namespace: string,
-  context: string,
   storage: StorageType,
   values: string
-}, { name: string }>('POST', pathParams => {
-  return `/api/releases/${pathParams.name}/upgrade`;
+}, {
+  id: number,
+  name: string,
+  cluster_id: number,
+  service_account_id: number,
+  }>('POST', pathParams => {
+  let { id, name, cluster_id, service_account_id } = pathParams;
+  return `/api/projects/${id}/releases/${name}/upgrade?cluster_id=${cluster_id}&service_account_id=${service_account_id}`;
 });
 
 const getTemplates = baseApi('GET', '/api/templates');
 
-const getRepos = baseApi('GET', '/api/repos');
+const getRepos = baseApi<{}, { id: number }>('GET', pathParams => {
+  return `/api/projects/${pathParams.id}/repos`;
+});
 
 const getBranches = baseApi<{}, { kind: string, repo: string }>('GET', pathParams => {
   return `/api/repos/${pathParams.kind}/${pathParams.repo}/branches`;
@@ -129,7 +150,7 @@ export default {
   getRepos,
   getUser,
   updateUser,
-  getContexts,
+  getClusters,
   getCharts,
   getChart,
   getChartComponents,

+ 14 - 4
dashboard/src/shared/types.tsx

@@ -1,9 +1,8 @@
-export interface KubeContextConfig {
-  cluster: string,
+export interface Cluster {
+  id: number,
   name: string,
-  selected?: boolean,
   server: string,
-  user: string
+  service_account_id: number
 }
 
 export interface ChartType {
@@ -108,4 +107,15 @@ export interface RepoType {
 export interface FileType {
   Path: string,
   Type: string
+}
+
+export interface ProjectType {
+  id: number,
+  name: string,
+  roles: {
+    id: number,
+    kind: string,
+    user_id: number,
+    project_id: number
+  }[]
 }