Просмотр исходного кода

Merge branch 'master' of https://github.com/porter-dev/porter into form-cluster-integration

mergin
Alexander Belanger 5 лет назад
Родитель
Сommit
07374ad23a

+ 1 - 1
README.md

@@ -1,5 +1,5 @@
 # Porter
-Porter is a **dashboard for Helm** with support for the following features:
+Porter is a **Kubernetes powered PaaS** with support for the following features:
 - User-generated [form overlays](https://docs.getporter.dev/docs/porter-templates) for managing `values.yaml`
 - Visualization of all Helm releases with filtering by namespace
 - In-depth view of releases, including revision histories and component graphs

+ 30 - 22
dashboard/src/components/ResourceTab.tsx

@@ -4,15 +4,15 @@ import styled from 'styled-components';
 import { kindToIcon } from '../shared/rosettaStone';
 
 type PropsType = {
-  kind: string,
+  label: string,
   name: string,
   handleClick?: () => void,
   selected?: boolean,
   isLast?: boolean,
   status?: {
     label: string,
-    available: number,
-    total: number,
+    available?: number,
+    total?: number,
   } | null
 };
 
@@ -58,13 +58,23 @@ export default class ResourceTab extends Component<PropsType, StateType> {
     }
   }
 
+  getStatusText = () => {
+    let { status } = this.props;
+    if (status.available && status.total) {
+      return `${status.available}/${status.total}`;
+    } else if (status.label) {
+      return status.label;
+    }
+  }
+
   renderStatus = () => {
     let { status } = this.props;
     if (status) {
       return (
         <Status>
+          {this.getStatusText()}
+
           <StatusColor status={status.label} />
-          {status.available}/{status.total}
         </Status>
       );
     }
@@ -81,11 +91,11 @@ export default class ResourceTab extends Component<PropsType, StateType> {
   }
 
   render() {
-    let { kind, name, children, isLast } = this.props;
+    let { label, name, children, isLast, handleClick } = this.props;
     return (
       <StyledResourceTab 
         isLast={isLast}
-        onClick={() => this.props.handleClick && this.props.handleClick()}
+        onClick={() => handleClick && handleClick()}
       >
         <ResourceHeader
           hasChildren={this.props.children && true}
@@ -96,11 +106,11 @@ export default class ResourceTab extends Component<PropsType, StateType> {
             }
           }}
         >
-          {this.renderDropdownIcon()}
           <Info>
+            {this.renderDropdownIcon()}
             <Metadata>
-              {this.renderIcon(kind)}
-              {kind}
+              {this.renderIcon(label)}
+              {label}
               <ResourceName
                 showKindLabels={true}
                 onMouseOver={() => { this.setState({ showTooltip: true }) }}
@@ -110,8 +120,8 @@ export default class ResourceTab extends Component<PropsType, StateType> {
               </ResourceName>
               {this.renderTooltip(name)}
             </Metadata>
-            {this.renderStatus()}
           </Info>
+          {this.renderStatus()}
         </ResourceHeader>
         {this.renderExpanded()}
       </StyledResourceTab>
@@ -161,18 +171,18 @@ const ResourceHeader = styled.div`
   height: 50px;
   display: flex;
   align-items: center;
+  justify-content: space-between;
   color: #ffffff66;
   user-select: none;
   padding: 8px 18px;
   padding-left: ${(props: { expanded: boolean, hasChildren: boolean }) => props.hasChildren ? '10px' : '22px'};
-  text-transform: capitalize;
-  cursor: pointer;
+  cursor: ${(props: { expanded: boolean, hasChildren: boolean }) => props.hasChildren ? 'pointer' : ''};
   background: ${(props: { expanded: boolean, hasChildren: boolean }) => props.expanded ? '#ffffff11' : ''};
   :hover {
-    background: #ffffff18;
+    background: ${(props: { expanded: boolean, hasChildren: boolean }) => props.hasChildren ? '#ffffff18' : ''};
 
     > i {
-      background: #ffffff22;
+      background: ${(props: { expanded: boolean, hasChildren: boolean }) => props.hasChildren ? '#ffffff22' : ''};
     }
   }
 `;
@@ -180,9 +190,8 @@ const ResourceHeader = styled.div`
 const Info = styled.div`
   display: flex;
   flex-direction: row;
-  justify-content: space-between;
   align-items: center;
-  width: calc(100% - 30px);
+  width: calc(100% - 50px);
   height: 100%;
 `;
 
@@ -195,7 +204,6 @@ const Metadata = styled.div`
 
 const Status = styled.div`
   display: flex;
-  width: 50px;
   font-size: 12px;
   text-transform: capitalize;
   justify-content: flex-end;
@@ -210,11 +218,11 @@ const Status = styled.div`
 `;
 
 const StatusColor = styled.div`
-  margin-right: 7px;
-  width: 7px;
-  min-width: 7px;
-  height: 7px;
-  background: ${(props: { status: string }) => (props.status === 'running' ? '#4797ff' : props.status === 'failed' ? "#ed5f85" : "#f5cb42")};
+  margin-left: 12px;
+  width: 8px;
+  min-width: 8px;
+  height: 8px;
+  background: ${(props: { status: string }) => (props.status === 'running' || props.status === 'Ready' ? '#4797ff' : props.status === 'failed' ? "#ed5f85" : "#f5cb42")};
   border-radius: 20px;
 `;
 

+ 26 - 11
dashboard/src/components/values-form/ValuesForm.tsx

@@ -12,13 +12,13 @@ import InputRow from './InputRow';
 import SelectRow from './SelectRow';
 import Helper from './Helper';
 import Heading from './Heading';
+import ResourceTab from '../ResourceTab';
 
 type PropsType = {
   onSubmit: (formValues: any) => void,
   sections?: Section[],
   disabled?: boolean,
   saveValuesStatus?: string | null,
-  config?: any, // Chart config object containing existing values
 };
 
 type StateType = any;
@@ -33,13 +33,7 @@ export default class ValuesForm extends Component<PropsType, StateType> {
         // If no name is assigned use values.yaml variable as identifier
         let key = item.name || item.variable;
         
-        let def = item.settings && item.settings.default;
-
-        // Set default value from chart config if available
-        if (this.props.config) {
-          let retrievedValue = _.get(this.props.config, key)
-          retrievedValue ? def = retrievedValue : null;
-        }
+        let def = (item.value && item.value[0]) || (item.settings && item.settings.default);
 
         switch (item.type) {
           case 'checkbox':
@@ -53,6 +47,7 @@ export default class ValuesForm extends Component<PropsType, StateType> {
             break;
           case 'select':
             formState[key] = def ? def : item.settings.options[0].value;
+            break;
           default:
         }
       });
@@ -62,7 +57,6 @@ export default class ValuesForm extends Component<PropsType, StateType> {
 
   // Initialize corresponding state fields for form blocks
   componentDidMount() {
-    console.log(this.props.sections)
     this.updateFormState();
   }
 
@@ -79,9 +73,25 @@ export default class ValuesForm extends Component<PropsType, StateType> {
       let key = item.name || item.variable;
       switch (item.type) {
         case 'heading':
-          return <Heading key={i}>{item.label}</Heading>
+          return <Heading key={i}>{item.label}</Heading>;
         case 'subtitle':
-          return <Helper key={i}>{item.label}</Helper>
+          return <Helper key={i}>{item.label}</Helper>;
+        case 'resource-list':
+          return (
+            <ResourceList>
+              {
+                item.value.map((resource: any, i: number) => {
+                  return (
+                    <ResourceTab
+                      label={resource.label}
+                      name={resource.name}
+                      status={{ label: resource.status }}
+                    />
+                  );
+                })
+              }
+            </ResourceList>
+          );
         case 'checkbox':
           return (
             <CheckboxRow
@@ -169,6 +179,11 @@ export default class ValuesForm extends Component<PropsType, StateType> {
 
 ValuesForm.contextType = Context;
 
+const ResourceList = styled.div`
+  margin-bottom: 15px;
+  margin-top: 20px;
+`;
+
 const DarkMatter = styled.div`
   margin-top: 0px;
 `;

+ 4 - 24
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -23,7 +23,7 @@ type StateType = {
 
 export default class ClusterDashboard extends Component<PropsType, StateType> {
   state = {
-    namespace: '',
+    namespace: 'default',
     currentChart: null as (ChartType | null)
   }
 
@@ -35,27 +35,6 @@ export default class ClusterDashboard extends Component<PropsType, StateType> {
     }
   }
 
-  // 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,
-      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;
@@ -80,8 +59,9 @@ export default class ClusterDashboard extends Component<PropsType, StateType> {
     if (this.state.currentChart) {
       return (
         <ExpandedChart
+          namespace={this.state.namespace}
+          currentCluster={this.props.currentCluster}
           currentChart={this.state.currentChart}
-          refreshChart={this.refreshChart}
           setCurrentChart={(x: ChartType | null) => this.setState({ currentChart: x })}
           setSidebar={setSidebar}
           setCurrentView={setCurrentView} // Link to integrations from chart settings
@@ -123,7 +103,7 @@ export default class ClusterDashboard extends Component<PropsType, StateType> {
         <ChartList
           currentCluster={currentCluster}
           namespace={this.state.namespace}
-          setCurrentChart={(x: ChartType) => this.setState({ currentChart: x })}
+          setCurrentChart={(x: ChartType | null) => this.setState({ currentChart: x })}
         />
       </div>
     );

+ 63 - 83
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -4,7 +4,7 @@ import yaml from 'js-yaml';
 import close from '../../../../assets/close.png';
 import _ from 'lodash';
 
-import { ResourceType, ChartType, StorageType, ChoiceType } from '../../../../shared/types';
+import { ResourceType, ChartType, StorageType, Cluster } from '../../../../shared/types';
 import { Context } from '../../../../shared/Context';
 import api from '../../../../shared/api';
 
@@ -19,18 +19,20 @@ import SettingsSection from './SettingsSection';
 import { NavLink } from 'react-router-dom';
 
 type PropsType = {
+  namespace: string,
   currentChart: ChartType,
+  currentCluster: Cluster,
   setCurrentChart: (x: ChartType | null) => void,
-  refreshChart: () => void,
   setSidebar: (x: boolean) => void,
   setCurrentView: (x: string) => void,
 };
 
 type StateType = {
+  loading: boolean,
   showRevisions: boolean,
   components: ResourceType[],
   podSelectors: string[]
-  revisionPreview: ChartType | null,
+  isPreview: boolean,
   devOpsMode: boolean,
   tabOptions: any[],
   tabContents: any,
@@ -39,20 +41,13 @@ type StateType = {
   forceRefreshRevisions: boolean, // Update revisions after upgrading values
 };
 
-/*
-  TODO: consolidate revisionPreview and currentChart (currentChart can just be the initial state)
-  In general, tab management for ExpandedChart should be refactored. Cases to handle:
-  - Hiding logs, deploy, and settings tabs when previewing old charts
-  - Toggling additional DevOps tabs
-  - Handling the currently selected tab becoming hidden (for both preview and DevOps)
-  As part of consolidating currentChart and revisionPreview, can add an isPreview bool.
-*/
 export default class ExpandedChart extends Component<PropsType, StateType> {
   state = {
+    loading: true,
     showRevisions: false,
     components: [] as ResourceType[],
     podSelectors: [] as string[],
-    revisionPreview: null as (ChartType | null),
+    isPreview: false,
     devOpsMode: localStorage.getItem('devOpsMode') === 'true',
     tabOptions: [] as any[],
     tabContents: [] as any,
@@ -61,6 +56,33 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     forceRefreshRevisions: false,
   }
 
+  // Retrieve full chart data (includes form and values)
+  getChartData = (chart: ChartType) => {
+    let { currentProject } = this.context;
+    let { currentCluster, setCurrentChart } = this.props;
+    this.setState({ loading: true })
+    api.getChart('<token>', {
+      namespace: this.props.namespace,
+      cluster_id: currentCluster.id,
+      storage: StorageType.Secret
+    }, {
+      name: chart.name,
+      revision: chart.version,
+      id: currentProject.id
+    }, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+      } else {
+        setCurrentChart(res.data);
+        this.setState({ loading: false });
+
+        // After retrieving full chart data, update tabs and resources
+        this.updateTabs();
+        this.updateResources();
+      }
+    });
+  }
+  
   updateResources = () => {
     let { currentCluster, currentProject } = this.context;
     let { currentChart } = this.props;
@@ -82,42 +104,21 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     });
   }
 
+  refreshChart = () => this.getChartData(this.props.currentChart);
+
   componentDidMount() {
-    this.updateTabs();
-    this.updateResources();
+    this.getChartData(this.props.currentChart);
   }
 
   componentDidUpdate(prevProps: PropsType) {
+    /*
     if (this.props.currentChart !== prevProps.currentChart) {
       this.updateResources();
     }
+    */
   }
 
-  getFormData = (): any => {
-    return new Promise(resolve => {
-      let { files } = this.props.currentChart.chart;
-      for (let file of files) { 
-        if (file.name === 'form.yaml') {
-          let chartName = this.props.currentChart.chart.metadata.name;
-          api.getTemplateInfo('<token>', {}, {
-            name: chartName.toLowerCase().trim(),
-            version: 'latest',
-          }, (err: any, res: any) => {
-            if (err) {
-              console.log(err);
-              resolve(null);
-            } else {
-              console.log(res.data)
-              let { form } = res.data;
-              resolve(form);
-            }
-          });
-        }
-      };
-    });
-  }
-
-  upgradeValues = (rawValues: any) => {
+  onSubmit = (rawValues: any) => {
     let { currentProject, currentCluster, setCurrentError } = this.context;
 
     // Convert dotted keys to nested objects
@@ -130,7 +131,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     let valuesYaml = yaml.dump({ ...(this.props.currentChart.config as Object), ...values });
     
     this.setState({ saveValuesStatus: 'loading' });
-    this.props.refreshChart();
+    this.refreshChart();
     api.upgradeChartValues('<token>', {
       namespace: this.props.currentChart.namespace,
       storage: StorageType.Secret,
@@ -141,8 +142,8 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
       cluster_id: currentCluster.id,
     }, (err: any, res: any) => {
       if (err) {
-        setCurrentError(err);
         this.setState({ saveValuesStatus: 'error' });
+        console.log(err)
       } else {
         this.setState({ 
           saveValuesStatus: 'successful',
@@ -160,11 +161,11 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
       showRevisions,
       saveValuesStatus,
       tabOptions,
-      revisionPreview,
+      isPreview,
     } = this.state;
-    let { currentChart, refreshChart, setSidebar, setCurrentView } = this.props;
-    let chart = revisionPreview || currentChart;
-
+    let { currentChart, setSidebar, setCurrentView } = this.props;
+    let chart = currentChart;
+    
     switch (currentTab) {
       case 'status': 
         return (
@@ -178,7 +179,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
         return (
           <SettingsSection
             currentChart={chart}
-            refreshChart={refreshChart}
+            refreshChart={this.refreshChart}
             setCurrentView={setCurrentView}
           /> 
         );
@@ -207,7 +208,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
         return (
           <ValuesYaml
             currentChart={chart}
-            refreshChart={refreshChart}
+            refreshChart={this.refreshChart}
           />
         );
       default:
@@ -220,10 +221,9 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
               return (
                 <ValuesFormWrapper key={i}>
                   <ValuesForm 
-                    sections={tab.sections} 
-                    onSubmit={this.upgradeValues}
+                    sections={tab.sections}
+                    onSubmit={this.onSubmit}
                     saveValuesStatus={saveValuesStatus}
-                    config={chart.config}
                   />
                 </ValuesFormWrapper>
               );
@@ -233,12 +233,12 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     }
   }
 
-  async updateTabs() {
-    let formData = await this.getFormData();
+  updateTabs() {
+    let formData = this.props.currentChart.form;
     let tabOptions = [] as any[];
 
     // Generate form tabs if form.yaml exists
-    if (formData && formData.tabs) {
+    if (formData) {
       formData.tabs.map((tab: any, i: number) => {
         tabOptions.push({ value: '@' + tab.name, label: tab.label, sections: tab.sections });
       });
@@ -260,37 +260,17 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     }
 
     // Filter tabs if previewing an old revision
-    if (this.state.revisionPreview) {
+    if (this.state.isPreview) {
       let liveTabs = ['status', 'settings', 'deploy'];
       tabOptions = tabOptions.filter((tab: any) => !liveTabs.includes(tab.value));
     }
-    
+
     this.setState({ tabOptions });
   }
 
-  setRevisionPreview = (oldChart: ChartType) => {
-    let { currentCluster, currentProject } = this.context;
-    this.setState({ revisionPreview: oldChart }, () => this.updateTabs());
-
-    if (oldChart) {
-      api.getChartComponents('<token>', {
-        namespace: oldChart.namespace,
-        cluster_id: currentCluster.id,
-        storage: StorageType.Secret
-      }, {
-        id: currentProject.id,
-        name: oldChart.name,
-        revision: oldChart.version
-      }, (err: any, res: any) => {
-        if (err) {
-          console.log(err)
-        } else {
-          this.setState({ components: res.data.Objects, podSelectors: res.data.PodSelectors });
-        }
-      });
-    } else {
-      this.updateResources();
-    }
+  setRevision = (chart: ChartType, isCurrent?: boolean) => {
+    this.setState({ isPreview: !isCurrent });
+    this.getChartData(chart);
   }
 
   // TODO: consolidate with pop + push in refreshTabs
@@ -326,8 +306,8 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
   }
 
   render() {
-    let { currentChart, setCurrentChart, refreshChart } = this.props;
-    let chart = this.state.revisionPreview || currentChart;
+    let { currentChart, setCurrentChart } = this.props;
+    let chart = currentChart;
 
     return ( 
       <div>
@@ -362,8 +342,8 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
               showRevisions={this.state.showRevisions}
               toggleShowRevisions={() => this.setState({ showRevisions: !this.state.showRevisions })}
               chart={chart}
-              refreshChart={refreshChart}
-              setRevisionPreview={this.setRevisionPreview}
+              refreshChart={this.refreshChart}
+              setRevision={this.setRevision}
               forceRefreshRevisions={this.state.forceRefreshRevisions}
               refreshRevisionsOff={() => this.setState({ forceRefreshRevisions: false })}
             />
@@ -373,7 +353,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
             currentTab={this.state.currentTab}
             setCurrentTab={(x: string) => this.setState({ currentTab: x })}
             options={this.state.tabOptions}
-            color={this.state.revisionPreview ? '#f5cb42' : null}
+            color={this.state.isPreview ? '#f5cb42' : null}
             addendum={
               <TabButton onClick={this.toggleDevOpsMode} devOpsMode={this.state.devOpsMode}>
                 <i className="material-icons">offline_bolt</i> DevOps Mode

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

@@ -48,7 +48,7 @@ export default class ListSection extends Component<PropsType, StateType> {
           key={i}
           handleClick={() => this.setState({ yaml: rawYaml })}
           selected={this.state.yaml === rawYaml}
-          kind={resource.Kind}
+          label={resource.Kind}
           name={resource.Name}
           isLast={i === this.props.components.length - 1}
         />

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

@@ -1,172 +0,0 @@
-import React, { Component } from 'react';
-import styled from 'styled-components';
-import yaml from 'js-yaml';
-
-import { kindToIcon } from '../../../../shared/rosettaStone';
-import { ResourceType } from '../../../../shared/types';
-import YamlEditor from '../../../../components/YamlEditor';
-
-
-type PropsType = {
-  resource: ResourceType,
-  toggleKindLabels: () => void,
-  showKindLabels: boolean
-};
-
-type StateType = {
-  expanded: boolean,
-  RawYAML: string
-};
-
-// A single resource block in the expanded chart list view
-export default class ResourceItem extends Component<PropsType, StateType> {
-  state = {
-    expanded: false,
-    RawYAML: yaml.dump(this.props.resource.RawYAML)
-  }
-
-  // Handle previewing old revisions
-  componentDidUpdate(prevProps: PropsType) {
-    if (prevProps.resource.RawYAML !== this.props.resource.RawYAML) {
-      this.setState({ RawYAML: yaml.dump(this.props.resource.RawYAML) });
-    }
-  }
-
-  renderIcon = (kind: string) => {
-
-    let icon = 'tonality';
-    if (Object.keys(kindToIcon).includes(kind)) {
-      icon = kindToIcon[kind]; 
-    }
-    
-    return (
-      <IconWrapper>
-        <i className="material-icons">{icon}</i>
-      </IconWrapper>
-    );
-  }
-
-  renderExpanded = () => {
-    if (this.state.expanded) {
-      return (
-        <ExpandWrapper>
-          <YamlEditor
-            value={this.state.RawYAML}
-            onChange={(e: any) => this.setState({ RawYAML: e })}
-            height='300px'
-            border={true}
-            readOnly={true}
-          />
-        </ExpandWrapper>
-      );
-    }
-  }
-
-  render() {
-    let { resource, showKindLabels, toggleKindLabels } = this.props;
-    return (
-      <StyledResourceItem>
-        <ResourceHeader
-          expanded={this.state.expanded}
-          onClick={() => this.setState({ expanded: !this.state.expanded })}
-        >
-          <i className="material-icons">arrow_right</i>
-
-          <ClickWrapper onClick={toggleKindLabels}>
-            {this.renderIcon(resource.Kind)}
-            {showKindLabels ? `${resource.Kind}` : null}
-          </ClickWrapper>
-
-          <ResourceName
-            showKindLabels={showKindLabels}
-          >
-            {resource.Name}
-          </ResourceName>
-        </ResourceHeader>
-        {this.renderExpanded()}
-      </StyledResourceItem>
-    );
-  }
-}
-
-const ExpandWrapper = styled.div`
-  padding: 12px;
-  animation: expandResource 0.3s;
-  animation-timing-function: ease-out;
-  overflow: hidden;
-  @keyframes expandResource {
-    from { height: 0px }
-    to { height: 300px }
-  }
-`;
-
-const StyledResourceItem = styled.div`
-  border-bottom: 1px solid #606166;
-`;
-
-const BigPlaceholder = styled.div`
-  height: 200px;
-  width: 100%;
-  background: blue;
-`;
-
-const ClickWrapper = styled.div`
-  display: flex;
-  align-items: center;
-`;
-
-const ResourceName = styled.div`
-  color: #ffffff;
-  margin-left: ${(props: { showKindLabels: boolean }) => props.showKindLabels ? '10px' : ''};
-  text-transform: none;
-`;
-
-const IconWrapper = styled.div`
-  width: 25px;
-  height: 25px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-
-  > i {
-    font-size: 16px;
-    color: #ffffff;
-    margin-right: 14px;
-  }
-`;
-
-const ResourceHeader = styled.div`
-  width: 100%;
-  height: 60px;
-  display: flex;
-  color: #ffffff66;
-  align-items: center;
-  padding: 15px 13px;
-  text-transform: capitalize;
-  cursor: pointer;
-  background: ${(props: { expanded: boolean }) => props.expanded ? '#ffffff11' : ''};
-  :hover {
-    background: #ffffff18;
-
-    > i {
-      background: #ffffff22;
-    }
-  }
-
-  > i {
-    margin-right: 13px;
-    font-size: 20px;
-    color: #ffffff66;
-    cursor: pointer;
-    border-radius: 20px;
-    background: ${(props: { expanded: boolean }) => props.expanded ? '#ffffff18' : ''};
-    transform: ${(props: { expanded: boolean }) => props.expanded ? 'rotate(180deg)' : ''};
-    animation: ${(props: { expanded: boolean }) => props.expanded ? 'quarterTurn 0.3s' : ''};
-    animation-fill-mode: forwards;
-
-    @keyframes quarterTurn {
-      from { transform: rotate(0deg) }
-      to { transform: rotate(90deg) }
-    }
-  }
-`;

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

@@ -13,7 +13,7 @@ type PropsType = {
   toggleShowRevisions: () => void,
   chart: ChartType,
   refreshChart: () => void,
-  setRevisionPreview: (preview: ChartType) => void
+  setRevision: (x: ChartType, isCurrent?: boolean) => void
   forceRefreshRevisions: boolean,
   refreshRevisionsOff: () => void,
 };
@@ -34,7 +34,7 @@ export default class RevisionSection extends Component<PropsType, StateType> {
     maxVersion: 0, // Track most recent version even when previewing old revisions
   }
 
-  refreshHistory = () => {
+  refreshHistory = (callback?: () => void) => {
     let { chart } = this.props;
     let { currentCluster, currentProject } = this.context;
     api.getRevisions('<token>', {
@@ -47,6 +47,7 @@ export default class RevisionSection extends Component<PropsType, StateType> {
       } else {
         res.data.sort((a: ChartType, b: ChartType) => { return -(a.version - b.version) });
         this.setState({ revisions: res.data, maxVersion: res.data[0].version });
+        callback && callback();
       }
     });
   }
@@ -59,8 +60,11 @@ export default class RevisionSection extends Component<PropsType, StateType> {
   componentDidUpdate(prevProps: PropsType) {
     if (this.props.forceRefreshRevisions) {
       this.props.refreshRevisionsOff();
-      this.props.refreshChart();
-      this.refreshHistory();
+
+      // Force refresh occurs on submit -> set current to newest
+      this.refreshHistory(() => {
+        this.props.setRevision(this.state.revisions[0], true);
+      });
     } else if (this.props.chart !== prevProps.chart) {
       this.refreshHistory();
     }
@@ -94,9 +98,9 @@ export default class RevisionSection extends Component<PropsType, StateType> {
         this.setState({ loading: false });
       } else {
         this.setState({ loading: false });
-        this.props.refreshChart();
-        this.refreshHistory();
-        this.props.setRevisionPreview(null);
+        this.refreshHistory(() => {
+          this.props.setRevision(this.state.revisions[0], true);
+        });
       }
     });
   }
@@ -104,9 +108,9 @@ export default class RevisionSection extends Component<PropsType, StateType> {
   handleClickRevision = (revision: ChartType) => {
     let isCurrent = revision.version === this.state.maxVersion;
     if (isCurrent) {
-      this.props.setRevisionPreview(null);
+      this.props.setRevision(revision, true);
     } else {
-      this.props.setRevisionPreview(revision);
+      this.props.setRevision(revision);
     }
   }
 

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

@@ -32,7 +32,7 @@ export default class SettingsSection extends Component<PropsType, StateType> {
 
   componentDidMount() {
     let image = this.props.currentChart.config.image;
-    if (image.repository) {
+    if (image?.repository) {
       this.setState({ selectedImageUrl: image.repository });
     }
   }

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

@@ -109,7 +109,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
     let status = (available == total) ? 'running' : 'waiting'
     return (
       <ResourceTab
-        kind={controller.kind}
+        label={controller.kind}
         name={controller.metadata.name}
         status={{ label: status, available, total }}
         isLast={isLast}

+ 4 - 4
dashboard/src/main/home/templates/expanded-template/LaunchTemplate.tsx

@@ -61,7 +61,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
 
     api.deployTemplate('<token>', {
       templateName: this.props.currentTemplate.name,
-      imageURL: "",
+      imageURL: this.state.selectedImageUrl,
       storage: StorageType.Secret,
       formValues: values,
       namespace: this.state.selectedNamespace,
@@ -91,8 +91,8 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
               key={tab.name}
               sections={tab.sections} 
               onSubmit={this.onSubmit}
-              // disabled={!this.state.selectedImageUrl || this.state.selectedImageUrl === ''}
               disabled={false}
+              // disabled={!this.state.selectedImageUrl || this.state.selectedImageUrl === ''}
               saveValuesStatus={this.state.saveValuesStatus}
             />
           </ValuesFormWrapper>
@@ -195,7 +195,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
           />
         </ClusterSection>
 
-        {/* <Subtitle>Select the container image you would like to connect to this template (optional).</Subtitle>
+        <Subtitle>Select the container image you would like to connect to this template (optional).</Subtitle>
         <Br />
         <ImageSelector
           selectedTag={this.state.selectedTag}
@@ -206,7 +206,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
           setCurrentView={this.props.setCurrentView}
         />
 
-        <br /> */}
+        <br />
         <Subtitle>Configure additional settings for this template (optional).</Subtitle>
         <TabRegion
           options={this.state.tabOptions}

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

@@ -28,6 +28,7 @@ export interface ChartType {
       name: string,
     }[],
   },
+  form?: FormYAML,
   config: any,
   version: number,
   namespace: string
@@ -84,7 +85,7 @@ export interface FormYAML {
     name: string,
     label: string,
     sections?: Section[]
-  }[]
+  }[],
 }
 
 export interface Section {
@@ -99,6 +100,7 @@ export interface FormElement {
   label: string,
   name?: string,
   variable?: string,
+  value?: any,
   settings?: {
     default?: number | string | boolean,
     options?: any[],

+ 0 - 2
internal/helm/loader/loader.go

@@ -79,8 +79,6 @@ func LoadChart(repoURL, chartName, chartVersion string) (*chart.Chart, error) {
 
 	data, err := ioutil.ReadAll(resp.Body)
 
-	// fmt.Println("DATA IS", string(data))
-
 	if err != nil {
 		return nil, err
 	}

+ 1 - 0
server/api/release_handler.go

@@ -300,6 +300,7 @@ func (app *App) HandleGetReleaseControllers(w http.ResponseWriter, r *http.Reque
 	// get current status of each controller
 	// TODO: refactor with type assertion
 	for _, c := range controllers {
+		c.Namespace = form.ReleaseForm.Form.Namespace
 		switch c.Kind {
 		case "Deployment":
 			rc, err := k8sAgent.GetDeployment(c)

+ 1 - 0
server/api/template_handler.go

@@ -72,6 +72,7 @@ func (app *App) HandleReadTemplate(w http.ResponseWriter, r *http.Request) {
 	chart, err := loader.LoadChart(form.RepoURL, form.Name, form.Version)
 
 	if err != nil {
+		fmt.Println("ERROR LOADING CHART", form.RepoURL, form.Name, form.Version, err)
 		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
 		return
 	}