Browse Source

ExpandedChart additional tabs and preview casing frontend

jusrhee 5 years ago
parent
commit
a6402438b9

+ 4 - 0
dashboard/src/assets/launch.svg

@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.4" d="M22 12.0048C22 17.5137 17.5116 22 12 22C6.48842 22 2 17.5137 2 12.0048C2 6.48625 6.48842 2 12 2C17.5116 2 22 6.48625 22 12.0048Z" fill="white"/>
+<path d="M16 12.0049C16 12.2576 15.9205 12.5113 15.7614 12.7145C15.7315 12.7543 15.5923 12.9186 15.483 13.0255L15.4233 13.0838C14.5881 13.9694 12.5099 15.3011 11.456 15.7278C11.456 15.7375 10.8295 15.9913 10.5312 16H10.4915C10.0341 16 9.60653 15.7482 9.38778 15.34C9.26847 15.1154 9.15909 14.4642 9.14915 14.4554C9.05966 13.8712 9 12.9769 9 11.9951C9 10.9657 9.05966 10.0316 9.16903 9.45808C9.16903 9.44836 9.27841 8.92345 9.34801 8.74848C9.45739 8.49672 9.65625 8.2819 9.90483 8.14581C10.1037 8.04957 10.3125 8 10.5312 8C10.7599 8.01069 11.1875 8.15553 11.3565 8.22357C12.4702 8.65128 14.598 10.051 15.4134 10.9064C15.5526 11.0425 15.7017 11.2087 15.7415 11.2467C15.9105 11.4605 16 11.723 16 12.0049Z" fill="white"/>
+</svg>

+ 8 - 21
dashboard/src/components/TabSelector.tsx

@@ -10,7 +10,8 @@ type PropsType = {
   currentTab: string,
   currentTab: string,
   options: selectOption[],
   options: selectOption[],
   setCurrentTab: (value: string) => void,
   setCurrentTab: (value: string) => void,
-  addendum?: any
+  addendum?: any,
+  color?: string
 };
 };
 
 
 type StateType = {
 type StateType = {
@@ -22,6 +23,7 @@ export default class TabSelector extends Component<PropsType, StateType> {
   }
   }
 
 
   renderTabList = () => {
   renderTabList = () => {
+    let color = this.props.color || '#949effcc';
     return (
     return (
       this.props.options.map((option: selectOption, i: number) => {
       this.props.options.map((option: selectOption, i: number) => {
         return (
         return (
@@ -29,7 +31,7 @@ export default class TabSelector extends Component<PropsType, StateType> {
             key={i}
             key={i}
             onClick={() => this.handleTabClick(option.value)}
             onClick={() => this.handleTabClick(option.value)}
             lastItem={i === this.props.options.length - 1}
             lastItem={i === this.props.options.length - 1}
-            highlight={option.value === this.props.currentTab}
+            highlight={option.value === this.props.currentTab ? color : null}
           >
           >
             {option.label}
             {option.label}
           </Tab>
           </Tab>
@@ -48,29 +50,14 @@ export default class TabSelector extends Component<PropsType, StateType> {
   }
   }
 }
 }
 
 
-const Highlight = styled.div`
-  width: 80%;
-  height: 1px;
-  margin-top: 5px;
-  background: #949EFFcc00;
-
-  opacity: 0;
-  animation: lineEnter 0.5s 0s;
-  animation-fill-mode: forwards;
-  @keyframes lineEnter {
-    from { width: 0%; opacity: 0; }
-    to   { width: 80%; opacity: 1; }
-  }
-`; 
-
 const Tab = styled.div`
 const Tab = styled.div`
   height: 30px;
   height: 30px;
-  margin-right: ${(props: { lastItem: boolean, highlight: boolean }) => props.lastItem ? '' : '30px'};
+  margin-right: ${(props: { lastItem: boolean, highlight: string }) => props.lastItem ? '' : '30px'};
   display: flex;
   display: flex;
   font-family: 'Work Sans', sans-serif;
   font-family: 'Work Sans', sans-serif;
   font-size: 13px;
   font-size: 13px;
   user-select: none;
   user-select: none;
-  color: ${(props: { lastItem: boolean, highlight: boolean }) => props.highlight ? '#949effcc' : '#aaaabb55'};
+  color: ${(props: { lastItem: boolean, highlight: string }) => props.highlight ? props.highlight : '#aaaabb55'};
   flex-direction: column;
   flex-direction: column;
   padding-top: 7px;
   padding-top: 7px;
   padding-bottom: 2px;
   padding-bottom: 2px;
@@ -78,9 +65,9 @@ const Tab = styled.div`
   align-items: center;
   align-items: center;
   cursor: pointer;
   cursor: pointer;
   white-space: nowrap;
   white-space: nowrap;
-  border-bottom: 1px solid ${(props: { lastItem: boolean, highlight: boolean }) => props.highlight ? '#949effcc' : 'none'};
+  border-bottom: 1px solid ${(props: { lastItem: boolean, highlight: string }) => props.highlight ? props.highlight : 'none'};
   :hover {
   :hover {
-    color: ${(props: { lastItem: boolean, highlight: boolean }) => props.highlight ? '' : '#aaaabb'};
+    color: ${(props: { lastItem: boolean, highlight: string }) => props.highlight ? '' : '#aaaabb'};
   }
   }
 `;
 `;
 
 

+ 1 - 1
dashboard/src/main/home/Home.tsx

@@ -24,7 +24,7 @@ export default class Home extends Component<PropsType, StateType> {
   state = {
   state = {
     forceSidebar: true,
     forceSidebar: true,
     showWelcome: false,
     showWelcome: false,
-    currentView: 'templates'
+    currentView: 'dashboard'
   }
   }
 
 
   renderDashboard = () => {
   renderDashboard = () => {

+ 43 - 14
dashboard/src/main/home/dashboard/expanded-chart/ExpandedChart.tsx

@@ -33,13 +33,17 @@ const tabOptions = [
   { label: 'Chart Overview', value: 'graph' },
   { label: 'Chart Overview', value: 'graph' },
   { label: 'Search Chart', value: 'list' },
   { label: 'Search Chart', value: 'list' },
   { label: 'Raw Values', value: 'values' },
   { label: 'Raw Values', value: 'values' },
-  { label: 'Logs', value: 'logs' },
+  { label: 'Detailed Logs', value: 'detailed-logs' },
+  { label: 'Deploy', value: 'deploy' },
+  { label: 'Settings', value: 'settings' },
 ];
 ];
 
 
 const basicOptions = [
 const basicOptions = [
-  { label: 'Update Values', value: 'values-form' },
+  { label: 'Values', value: 'values-form' },
   { label: 'Environment', value: 'environment' },
   { label: 'Environment', value: 'environment' },
   { label: 'Logs', value: 'logs' },
   { label: 'Logs', value: 'logs' },
+  { label: 'Deploy', value: 'deploy' },
+  { label: 'Settings', value: 'settings' },
 ];
 ];
 
 
 // TODO: consolidate revisionPreview and currentChart (currentChart can just be the initial state)
 // TODO: consolidate revisionPreview and currentChart (currentChart can just be the initial state)
@@ -83,17 +87,26 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     let { currentCluster } = this.context;
     let { currentCluster } = this.context;
     this.setState({ revisionPreview: oldChart });
     this.setState({ revisionPreview: oldChart });
 
 
-    api.getChartComponents('<token>', {
-      namespace: oldChart.namespace,
-      context: currentCluster,
-      storage: StorageType.Secret
-    }, { name: oldChart.name, revision: oldChart.version }, (err: any, res: any) => {
-      if (err) {
-        console.log(err)
-      } else {
-        this.setState({ components: res.data });
+    if (oldChart) {
+      api.getChartComponents('<token>', {
+        namespace: oldChart.namespace,
+        context: currentCluster,
+        storage: StorageType.Secret
+      }, { name: oldChart.name, revision: oldChart.version }, (err: any, res: any) => {
+        if (err) {
+          console.log(err)
+        } else {
+          this.setState({ components: res.data });
+        }
+      });
+
+      // Handle preview old chart while logs tab is open
+      if (this.state.currentTab === 'logs') {
+        this.setState({ currentTab: 'values-form' });
+      } else if (this.state.currentTab === 'detailed-logs') {
+        this.setState({ currentTab: 'graph' });
       }
       }
-    });
+    }
   }
   }
 
 
   toggleDevOpsMode = () => {
   toggleDevOpsMode = () => {
@@ -121,6 +134,21 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     return `${time} on ${date}`;
     return `${time} on ${date}`;
   }
   }
 
 
+  // Hide certain tabs when previewing old charts
+  getTabOptions = () => {
+    let options = basicOptions.slice();
+    if (this.state.devOpsMode) {
+      options = tabOptions.slice();
+    }
+
+    if (this.state.revisionPreview) {
+      options.pop();
+      options.pop();
+      options.pop();
+    }
+    return options;
+  }
+
   renderTabContents = () => {
   renderTabContents = () => {
     let { currentChart, refreshChart, setSidebar } = this.props;
     let { currentChart, refreshChart, setSidebar } = this.props;
     let chart = currentChart;
     let chart = currentChart;
@@ -199,7 +227,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
 
 
               <TagWrapper>
               <TagWrapper>
                 Namespace
                 Namespace
-              <NamespaceTag>
+                <NamespaceTag>
                   {chart.namespace}
                   {chart.namespace}
                 </NamespaceTag>
                 </NamespaceTag>
               </TagWrapper>
               </TagWrapper>
@@ -219,7 +247,8 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
 
 
             <TabSelectorWrapper>
             <TabSelectorWrapper>
               <TabSelector
               <TabSelector
-                options={this.state.devOpsMode ? tabOptions : basicOptions}
+                options={this.getTabOptions()}
+                color={this.state.revisionPreview ? '#f5cb42' : null}
                 currentTab={this.state.currentTab}
                 currentTab={this.state.currentTab}
                 setCurrentTab={(value: string) => this.setState({ currentTab: value })}
                 setCurrentTab={(value: string) => this.setState({ currentTab: value })}
                 addendum={
                 addendum={

+ 16 - 5
dashboard/src/main/home/dashboard/expanded-chart/RevisionSection.tsx

@@ -21,6 +21,7 @@ type StateType = {
   maxVersion: number
   maxVersion: number
 };
 };
 
 
+// TODO: handle refresh when new revision is generated from an old revision
 export default class RevisionSection extends Component<PropsType, StateType> {
 export default class RevisionSection extends Component<PropsType, StateType> {
   state = {
   state = {
     revisions: [] as ChartType[],
     revisions: [] as ChartType[],
@@ -89,13 +90,22 @@ export default class RevisionSection extends Component<PropsType, StateType> {
     });
     });
   }
   }
 
 
+  handleClickRevision = (revision: ChartType) => {
+    let isCurrent = revision.version === this.state.maxVersion;
+    if (isCurrent) {
+      this.props.setRevisionPreview(null);
+    } else {
+      this.props.setRevisionPreview(revision);
+    }
+  }
+
   renderRevisionList = () => {
   renderRevisionList = () => {
     return this.state.revisions.map((revision: ChartType, i: number) => {
     return this.state.revisions.map((revision: ChartType, i: number) => {
       let isCurrent = revision.version === this.state.maxVersion;
       let isCurrent = revision.version === this.state.maxVersion;
       return (
       return (
         <Tr
         <Tr
           key={i}
           key={i}
-          onClick={() => this.props.setRevisionPreview(revision)}
+          onClick={() => this.handleClickRevision(revision)}
           selected={this.props.chart.version === revision.version}
           selected={this.props.chart.version === revision.version}
         >
         >
           <Td>{revision.version}</Td>
           <Td>{revision.version}</Td>
@@ -172,6 +182,7 @@ export default class RevisionSection extends Component<PropsType, StateType> {
       <div>
       <div>
         <RevisionHeader
         <RevisionHeader
           showRevisions={this.props.showRevisions}
           showRevisions={this.props.showRevisions}
+          isCurrent={isCurrent}
           onClick={this.props.toggleShowRevisions}
           onClick={this.props.toggleShowRevisions}
         >
         >
           {isCurrent ? `Current Revision` : `Previewing Revision (Not Deployed)`} - <Revision>No. {this.props.chart.version}</Revision>
           {isCurrent ? `Current Revision` : `Previewing Revision (Not Deployed)`} - <Revision>No. {this.props.chart.version}</Revision>
@@ -343,7 +354,7 @@ const Revision = styled.div`
 `;
 `;
 
 
 const RevisionHeader = styled.div`
 const RevisionHeader = styled.div`
-  color: #ffffff66;
+  color: ${(props: { showRevisions: boolean, isCurrent: boolean }) => props.isCurrent ? '#ffffff66' : '#f5cb42'};
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   height: 40px;
   height: 40px;
@@ -351,7 +362,7 @@ const RevisionHeader = styled.div`
   width: 100%;
   width: 100%;
   padding-left: 15px;
   padding-left: 15px;
   cursor: pointer;
   cursor: pointer;
-  background: ${(props: { showRevisions: boolean }) => props.showRevisions ? '#ffffff11' : ''};
+  background: ${(props: { showRevisions: boolean, isCurrent: boolean }) => props.showRevisions ? '#ffffff11' : ''};
   :hover {
   :hover {
     background: #ffffff18;
     background: #ffffff18;
     > i {
     > i {
@@ -364,8 +375,8 @@ const RevisionHeader = styled.div`
     font-size: 20px;
     font-size: 20px;
     cursor: pointer;
     cursor: pointer;
     border-radius: 20px;
     border-radius: 20px;
-    background: ${(props: { showRevisions: boolean }) => props.showRevisions ? '#ffffff18' : ''};
-    transform: ${(props: { showRevisions: boolean }) => props.showRevisions ? 'rotate(180deg)' : ''};
+    background: ${(props: { showRevisions: boolean, isCurrent: boolean }) => props.showRevisions ? '#ffffff18' : ''};
+    transform: ${(props: { showRevisions: boolean, isCurrent: boolean }) => props.showRevisions ? 'rotate(180deg)' : ''};
   }
   }
 `;
 `;
 
 

+ 95 - 37
dashboard/src/main/home/templates/Templates.tsx

@@ -3,9 +3,11 @@ import styled from 'styled-components';
 
 
 import { Context } from '../../../shared/Context';
 import { Context } from '../../../shared/Context';
 import api from '../../../shared/api';
 import api from '../../../shared/api';
+import { PorterChart } from '../../../shared/types';
 
 
 import TabSelector from '../../../components/TabSelector';
 import TabSelector from '../../../components/TabSelector';
-import { AnyNaptrRecord } from 'dns';
+import ExpandedTemplate from './expanded-template/ExpandedTemplate';
+import Loading from '../../../components/Loading';
 
 
 const tabOptions = [
 const tabOptions = [
   { label: 'Community Templates', value: 'community' }
   { label: 'Community Templates', value: 'community' }
@@ -15,60 +17,94 @@ type PropsType = {
 };
 };
 
 
 type StateType = {
 type StateType = {
+  currentChart: PorterChart | null,
   currentTab: string,
   currentTab: string,
-  porterCharts: any[]
+  porterCharts: PorterChart[],
+  loading: boolean,
+  error: boolean
 };
 };
 
 
 export default class Templates extends Component<PropsType, StateType> {
 export default class Templates extends Component<PropsType, StateType> {
   state = {
   state = {
+    currentChart: null as (PorterChart | null),
     currentTab: 'community',
     currentTab: 'community',
-    porterCharts: [] as any[]
+    porterCharts: [] as PorterChart[],
+    loading: false,
+    error: false,
   }
   }
 
 
   componentDidMount() {
   componentDidMount() {
+
     // Get templates
     // Get templates
+    this.setState({ loading: true });
     api.getTemplates('<token>', {}, {}, (err: any, res: any) => {
     api.getTemplates('<token>', {}, {}, (err: any, res: any) => {
       if (err) {
       if (err) {
-        console.log(err);
+        this.setState({ loading: false, error: true });
       } else {
       } else {
-        this.setState({ porterCharts: res.data });
+        this.setState({ porterCharts: res.data, loading: false, error: false });
       }
       }
     });
     });
   }
   }
 
 
   renderIcon = (icon: string) => {
   renderIcon = (icon: string) => {
     if (icon) {
     if (icon) {
-      return <Icon src={icon} />
+      return <Icon src={icon} />;
     }
     }
 
 
     return (
     return (
-        <Polymer><i className="material-icons">layers</i></Polymer>
-    )
+      <Polymer><i className="material-icons">layers</i></Polymer>
+    );
   }
   }
 
 
-  renderStackList = () => {
-    return this.state.porterCharts.map((template, i) => {
-      console.log(template)
+  renderTemplateList = () => {
+    let { loading, error, porterCharts } = this.state;
+
+    if (loading) {
+      return <LoadingWrapper><Loading /></LoadingWrapper>
+    } else if (error) {
       return (
       return (
-        <TemplateBlock key={i}>
-          {this.renderIcon(template.Icon)}
+        <Placeholder>
+          <i className="material-icons">error</i> Error retrieving templates.
+        </Placeholder>
+      );
+    } else if (porterCharts.length === 0) {
+      return (
+        <Placeholder>
+          <i className="material-icons">category</i> No templates found.
+        </Placeholder>
+      );
+    }
+
+    return this.state.porterCharts.map((template: PorterChart, i: number) => {
+      let { Name, Icon, Description } = template.Form;
+      return (
+        <TemplateBlock key={i} onClick={() => this.setState({ currentChart: template })}>
+          {Icon ? this.renderIcon(Icon) : this.renderIcon(template.Icon)}
           <TemplateTitle>
           <TemplateTitle>
-            {template.Form.Name ? template.Form.Name : template.Name}
+            {Name ? Name : template.Name}
           </TemplateTitle>
           </TemplateTitle>
           <TemplateDescription>
           <TemplateDescription>
-            {template.Form.Description ? template.Form.Description : template.Description}
+            {Description ? Description : template.Description}
           </TemplateDescription>
           </TemplateDescription>
         </TemplateBlock>
         </TemplateBlock>
       )
       )
-    })
+    });
   }
   }
-  
-  render() {
-    return ( 
-      <StyledTemplates>
-        <TemplatesWrapper>
+
+  renderContents = () => {
+    if (this.state.currentChart) {
+      return (
+        <ExpandedTemplate
+          currentChart={this.state.currentChart}
+          setCurrentChart={(currentChart: PorterChart) => this.setState({ currentChart })}
+        />
+      );
+    }
+
+    return (
+      <TemplatesWrapper>
         <TitleSection>
         <TitleSection>
-          <Title>Template Manager</Title>
+          <Title>Template Explorer</Title>
         </TitleSection>
         </TitleSection>
         <TabSelector
         <TabSelector
           options={tabOptions}
           options={tabOptions}
@@ -76,9 +112,16 @@ export default class Templates extends Component<PropsType, StateType> {
           setCurrentTab={(value: string) => this.setState({ currentTab: value })}
           setCurrentTab={(value: string) => this.setState({ currentTab: value })}
         />
         />
         <TemplateList>
         <TemplateList>
-          {this.renderStackList()}
+          {this.renderTemplateList()}
         </TemplateList>
         </TemplateList>
-        </TemplatesWrapper>
+      </TemplatesWrapper>
+    );
+  }
+  
+  render() {
+    return ( 
+      <StyledTemplates>
+        {this.renderContents()}
       </StyledTemplates>
       </StyledTemplates>
     );
     );
   }
   }
@@ -86,11 +129,29 @@ export default class Templates extends Component<PropsType, StateType> {
 
 
 Templates.contextType = Context;
 Templates.contextType = Context;
 
 
+const Placeholder = styled.div`
+  padding-top: 100px;
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #ffffff44;
+  font-size: 14px;
+
+  > i {
+    font-size: 18px;
+    margin-right: 12px;
+  }
+`;
+
+const LoadingWrapper = styled.div`
+  padding-top: 300px;
+`;
 
 
 const Icon = styled.img`
 const Icon = styled.img`
-  height: 50px;
-  margin-top: 28px;
-  margin-bottom: 15px;
+  height: 42px;
+  margin-top: 35px;
+  margin-bottom: 13px;
 `;
 `;
 
 
 const Polymer = styled.div`
 const Polymer = styled.div`
@@ -125,14 +186,6 @@ const TemplateTitle = styled.div`
   text-overflow: ellipsis;
   text-overflow: ellipsis;
 `;
 `;
 
 
-const CenterWrap = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex: 1;
-  padding-bottom: 15px;
-`;
-
 const TemplateBlock = styled.div`
 const TemplateBlock = styled.div`
   background: none;
   background: none;
   border: 1px solid #ffffff44;
   border: 1px solid #ffffff44;
@@ -152,9 +205,14 @@ const TemplateBlock = styled.div`
   cursor: pointer;
   cursor: pointer;
   color: #ffffff;
   color: #ffffff;
   position: relative;
   position: relative;
-
   :hover {
   :hover {
-    background: #ffffff08;
+    background: #ffffff11;
+  }
+
+  animation: fadeIn 0.3s 0s;
+  @keyframes fadeIn {
+    from { opacity: 0 }
+    to { opacity: 1 }
   }
   }
 `;
 `;
 
 

+ 147 - 0
dashboard/src/main/home/templates/expanded-template/ExpandedTemplate.tsx

@@ -0,0 +1,147 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import launch from '../../../../assets/launch.svg';
+
+import { PorterChart } from '../../../../shared/types';
+
+type PropsType = {
+  currentChart: PorterChart,
+  setCurrentChart: (x: PorterChart) => void
+};
+
+type StateType = {
+};
+
+export default class ExpandedTemplate extends Component<PropsType, StateType> {
+  state = {
+  }
+
+  renderIcon = (icon: string) => {
+    if (icon) {
+      return <Icon src={icon} />
+    }
+
+    return (
+      <Polymer><i className="material-icons">layers</i></Polymer>
+    );
+  }
+
+  render() {
+    let { Name, Icon, Description } = this.props.currentChart.Form;
+    let { currentChart } = this.props;
+
+    return (
+      <StyledExpandedTemplate>
+        <TitleSection>
+          <Flex>
+            <i className="material-icons" onClick={() => this.props.setCurrentChart(null)}>
+              keyboard_backspace
+            </i>
+            {Icon ? this.renderIcon(Icon) : this.renderIcon(currentChart.Icon)}
+            <Title>{Name ? Name : currentChart.Name}</Title>
+          </Flex>
+          <Button>
+            <img src={launch} />
+            Launch Template
+          </Button>
+        </TitleSection>
+      </StyledExpandedTemplate>
+    );
+  }
+}
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+
+  > i {
+    margin-left: 10px;
+    cursor: pointer;
+    font-size 24px;
+    color: #969Fbbaa;
+    padding: 3px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+  }
+`;
+
+const Button = styled.div`
+  height: 100%;
+  background: #616FEEcc;
+  :hover {
+    background: #505edddd;
+  }
+  color: white;
+  font-weight: 500;
+  font-size: 13px;
+  padding: 10px 15px;
+  border-radius: 3px;
+  cursor: pointer;
+  box-shadow: 0 5px 8px 0px #00000010;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+
+  > img {
+    width: 20px;
+    height: 20px;
+    display: flex;
+    align-items: center;
+    margin-right: 10px;
+    justify-content: center;
+  }
+`;
+
+const Icon = styled.img`
+  width: 27px;
+  margin-left: 14px;
+  margin-right: 4px;
+  margin-bottom: -1px;
+`;
+
+
+const Polymer = styled.div`
+  margin-bottom: -3px;
+
+  > i {
+    color: ${props => props.theme.containerIcon};
+    font-size: 24px;
+    margin-left: 12px;
+    margin-right: 3px;
+  }
+`;
+
+const Description = styled.div`
+  font-size: 14px;
+  font-family: 'Work Sans', sans-serif;
+  margin-left: 30px;
+  width: calc(100% - 60px);
+  height: 4em;
+  border-radius: 2px;
+  color: #aaaabb;
+  padding: 5px 10px;
+`;
+
+const Title = styled.div`
+  font-size: 24px;
+  font-weight: 600;
+  font-family: 'Work Sans', sans-serif;
+  margin-left: 10px;
+  border-radius: 2px;
+  color: #ffffff;
+`;
+
+const TitleSection = styled.div`
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  width: 100%;
+  align-items: center;
+`;
+
+const StyledExpandedTemplate = styled.div`
+  width: calc(90% - 10px);
+  padding-top: 20px;
+`;

+ 31 - 1
dashboard/src/shared/types.tsx

@@ -57,9 +57,39 @@ export interface EdgeType {
   target: number
   target: number
 }
 }
 
 
-
 export enum StorageType {
 export enum StorageType {
   Secret = 'secret',
   Secret = 'secret',
   ConfigMap = 'configmap',
   ConfigMap = 'configmap',
   Memory = 'memory'
   Memory = 'memory'
 }
 }
+
+// PorterChart represents a bundled Porter template
+export interface PorterChart {
+	Name: string,
+	Description: string,
+	Icon: string,
+	Form: FormYAML
+}
+
+// FormYAML represents a chart's values.yaml form abstraction
+export interface FormYAML {
+	Name: string,  
+	Icon: string,   
+	Description: string,   
+	Tags: string[],
+  Sections: {
+    Name: string,
+    Contents: FormElement[]
+  }[]
+}
+
+// FormElement represents a form element
+export interface FormElement {
+  Type: string,
+  Label: string,
+  Name: string,
+  Variable: string,
+  Settings: {
+    Default: number
+  }
+}

+ 2 - 0
server/api/template_handler.go

@@ -49,6 +49,7 @@ type PorterChart struct {
 // FormYAML represents a chart's values.yaml form abstraction
 // FormYAML represents a chart's values.yaml form abstraction
 type FormYAML struct {
 type FormYAML struct {
 	Name        string   `yaml:"name"`
 	Name        string   `yaml:"name"`
+	Icon        string   `yaml:"icon"`
 	Description string   `yaml:"description"`
 	Description string   `yaml:"description"`
 	Tags        []string `yaml:"tags"`
 	Tags        []string `yaml:"tags"`
 	Sections    []struct {
 	Sections    []struct {
@@ -66,6 +67,7 @@ type FormYAML struct {
 }
 }
 
 
 // HandleListTemplates retrieves a list of Porter templates
 // HandleListTemplates retrieves a list of Porter templates
+// TODO: test and reduce fragility (handle untar/parse error for individual charts)
 func (app *App) HandleListTemplates(w http.ResponseWriter, r *http.Request) {
 func (app *App) HandleListTemplates(w http.ResponseWriter, r *http.Request) {
 	resp, err := http.Get(baseURL + "index.yaml")
 	resp, err := http.Get(baseURL + "index.yaml")
 	if err != nil {
 	if err != nil {