ソースを参照

form.yaml ft. tab support launch template frontend

jusrhee 5 年 前
コミット
7ee80ea235

+ 23 - 5
dashboard/src/components/TabRegion.tsx

@@ -5,7 +5,7 @@ import TabSelector from './TabSelector';
 
 type PropsType = {
   options: { label: string, value: string }[],
-  contents: any,
+  tabContents: any,
   defaultTab?: string
 };
 
@@ -19,14 +19,24 @@ export default class TabRegion extends Component<PropsType, StateType> {
     currentTab: this.props.defaultTab
   }
 
-  componentDidMount() {
-    if (!this.props.defaultTab) {
+  setDefaultTab = () => {
+    if (!this.props.defaultTab && this.props.options[0]) {
       this.setState({ currentTab: this.props.options[0].value });
     }
   }
 
+  componentDidMount() {
+    this.setDefaultTab();
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if (prevProps.options !== this.props.options) {
+      this.setDefaultTab();
+    }
+  }
+
   renderTabContents = () => {
-    let found = this.props.contents.find((el: any) => el.value === this.state.currentTab);
+    let found = this.props.tabContents.find((el: any) => el.value === this.state.currentTab);
     if (found) {
       return found.component;
     }
@@ -40,14 +50,22 @@ export default class TabRegion extends Component<PropsType, StateType> {
           currentTab={this.state.currentTab}
           setCurrentTab={(x: string) => this.setState({ currentTab: x })}
         />
-
+        <Gap />
         {this.renderTabContents()}
       </StyledTabRegion>
     );
   }
 }
 
+const Gap = styled.div`
+  width: 100%;
+  background: none;
+  height: 30px;
+`;
+
 const StyledTabRegion = styled.div`
   width: 100%;
   height: 100%;
+  padding-bottom: 70px;
+  position: relative;
 `;

+ 2 - 2
dashboard/src/components/image-selector/ImageSelector.tsx

@@ -141,8 +141,7 @@ const Input = styled.input`
   outline: 0;
   background: none;
   border: 0;
-  max-width: 800px;
-  min-width: 300px;
+  width: calc(100% - 60px);
   color: white;
 `;
 
@@ -196,6 +195,7 @@ const ExpandedWrapper = styled.div`
 const Label = styled.div`
   display: flex;
   align-items: center;
+  flex: 1;
 
   > img {
     width: 18px;

+ 1 - 1
dashboard/src/components/values-form/InputRow.tsx

@@ -27,7 +27,7 @@ export default class InputRow extends Component<PropsType, StateType> {
             placeholder={placeholder}
             width={width}
             type={type}
-            value={value}
+            value={value || ''}
             onChange={(e: ChangeEvent<HTMLInputElement>) =>
               this.props.setValue(e.target.value)
             }

+ 21 - 10
dashboard/src/components/values-form/ValuesForm.tsx

@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
 import styled from 'styled-components';
 
-import { FormYAML, Section, FormElement } from '../../shared/types';
+import { Section, FormElement } from '../../shared/types';
 
 import SaveButton from '../SaveButton';
 import CheckboxRow from './CheckboxRow';
@@ -9,17 +9,16 @@ import InputRow from './InputRow';
 import SelectRow from './SelectRow';
 
 type PropsType = {
-  formData?: FormYAML
+  sections?: Section[]
 };
 
 type StateType = any;
 
 export default class ValuesForm extends Component<PropsType, StateType> {
 
-  // Initialize corresponding state fields for form blocks
-  componentDidMount() {
+  updateFormState() {
     let formState: any = {};
-    this.props.formData.Sections.forEach((section: Section, i: number) => {
+    this.props.sections.forEach((section: Section, i: number) => {
       section.Contents.forEach((item: FormElement, i: number) => {
 
         // If no name is assigned use values.yaml variable as identifier
@@ -34,7 +33,7 @@ export default class ValuesForm extends Component<PropsType, StateType> {
             formState[key] = def ? def : '';
             break;
           case 'number-input':
-            formState[key] = def ? def : '';
+            formState[key] = def.toString() ? def.toString() : '';
             break;
           case 'select':
             formState[key] = def ? def : item.Settings.Options[0].Value;
@@ -45,6 +44,17 @@ export default class ValuesForm extends Component<PropsType, StateType> {
     this.setState(formState);
   }
 
+  // Initialize corresponding state fields for form blocks
+  componentDidMount() {
+    this.updateFormState();
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if (this.props.sections !== prevProps.sections) {
+      this.updateFormState();
+    }
+  }
+
   renderSection = (section: Section) => {
     return section.Contents.map((item: FormElement, i: number) => {
 
@@ -68,7 +78,7 @@ export default class ValuesForm extends Component<PropsType, StateType> {
           return (
             <InputRow
               key={i}
-              type={'text'}
+              type='text'
               value={this.state[key]}
               setValue={(x: string) => this.setState({ [key]: x })}
               label={item.Label}
@@ -79,9 +89,9 @@ export default class ValuesForm extends Component<PropsType, StateType> {
           return (
             <InputRow
               key={i}
-              type={'number'}
+              type='number'
               value={this.state[key]}
-              setValue={(x: string) => this.setState({ [key]: parseInt(x) })}
+              setValue={(x: string) => this.setState({ [key]: x })}
               label={item.Label}
               unit={item.Settings ? item.Settings.Unit : null}
             />
@@ -104,7 +114,7 @@ export default class ValuesForm extends Component<PropsType, StateType> {
 
   renderFormContents = () => {
     if (this.state) {
-      return this.props.formData.Sections.map((section: Section, i: number) => {
+      return this.props.sections.map((section: Section, i: number) => {
 
         // Hide collapsible section if deciding field is false
         if (section.ShowIf) {
@@ -133,6 +143,7 @@ export default class ValuesForm extends Component<PropsType, StateType> {
           text='Deploy'
           onClick={() => console.log(this.state)}
           status={null}
+          makeFlush={true}
         />
       </Wrapper>
     );

+ 0 - 9
dashboard/src/main/home/Home.tsx

@@ -11,7 +11,6 @@ import ClusterDashboard from './cluster-dashboard/ClusterDashboard';
 import Loading from '../../components/Loading';
 import Templates from './templates/Templates';
 import Integrations from "./integrations/Integrations";
-import LaunchTemplateModal from './modals/LaunchTemplateModal';
 import CreateProjectModal from './modals/CreateProjectModal';
 import UpdateProjectModal from './modals/UpdateProjectModal';
 import ClusterInstructionsModal from './modals/ClusterInstructionsModal';
@@ -104,14 +103,6 @@ export default class Home extends Component<PropsType, StateType> {
     let { currentModal, setCurrentModal, currentProject } = this.context;
     return (
       <StyledHome>
-        <ReactModal
-          isOpen={currentModal === 'LaunchTemplateModal'}
-          onRequestClose={() => setCurrentModal(null, null)}
-          style={MediumModalStyles}
-          ariaHideApp={false}
-        >
-          <LaunchTemplateModal />
-        </ReactModal>
         <ReactModal
           isOpen={currentModal === 'CreateProjectModal'}
           onRequestClose={() => currentProject ? setCurrentModal(null, null) : null }

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

@@ -297,9 +297,12 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
       );
     } else if (this.state.currentTab === 'values-form') {
       return (
+        null
+        /*
         <ValuesFormWrapper>
           <ValuesForm formData={dummyForm} />
         </ValuesFormWrapper>
+        */
       );
     } else if (this.state.currentTab === 'settings') {
       return (
@@ -601,7 +604,6 @@ const StyledExpandedChart = styled.div`
   animation-fill-mode: forwards;
   padding: 25px; 
   display: flex;
-
   flex-direction: column;
 
   @keyframes floatIn {

+ 0 - 294
dashboard/src/main/home/modals/LaunchTemplateModal.tsx

@@ -1,294 +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 { Cluster, RepoType } from '../../../shared/types';
-
-import SaveButton from '../../../components/SaveButton';
-import Selector from '../../../components/Selector';
-import RepoSelector from '../../../components/repo-selector/RepoSelector';
-import ValuesForm from '../../../components/values-form/ValuesForm';
-
-type PropsType = {
-};
-
-type StateType = {
-  currentView: string,
-  clusterOptions: { label: string, value: string }[],
-  selectedCluster: string,
-  selectedRepo: RepoType | null,
-  selectedBranch: string,
-  subdirectory: string,
-};
-
-export default class LaunchTemplateModal extends Component<PropsType, StateType> {
-  state = {
-    currentView: 'repo',
-    clusterOptions: [] as { label: string, value: string }[],
-    selectedCluster: this.context.currentCluster.name,
-    selectedRepo: null as RepoType | null,
-    selectedBranch: '',
-    subdirectory: '',
-  };
-  
-  componentDidMount() {
-    let { currentProject } = this.context;
-
-    // TODO: query with selected filter once implemented
-    api.getClusters('<token>', {}, { id: currentProject.id }, (err: any, res: any) => {
-      if (err) {
-        // console.log(err)
-      } else if (res.data) {
-        let clusterOptions = res.data.map((x: Cluster) => { return { label: x.name, value: x.name } });
-        if (res.data.length > 0) {
-          this.setState({ clusterOptions });
-        }
-      }
-    });
-  }
-
-  renderIcon = (icon: string) => {
-    if (icon) {
-      return <Icon src={icon} />
-    }
-
-    return (
-      <Polymer><i className="material-icons">layers</i></Polymer>
-    );
-  }
-
-  renderContents = () => {
-    if (this.state.currentView === 'repo') {
-      return (
-        <div>
-          <Subtitle>Select the source and branch you would like to use</Subtitle>
-          <RepoSelector
-            forceExpanded={true}
-            selectedRepo={this.state.selectedRepo}
-            selectedBranch={this.state.selectedBranch}
-            subdirectory={this.state.subdirectory}
-            setSelectedRepo={(selectedRepo: RepoType) => this.setState({ selectedRepo })}
-            setSelectedBranch={(selectedBranch: string) => this.setState({ selectedBranch })}
-            setSubdirectory={(subdirectory: string) => this.setState({ subdirectory })}
-          />
-          <SaveButton
-            disabled={this.state.selectedBranch === ''}
-            text='Continue'
-            onClick={() => this.setState({ currentView: 'values'})}
-          />
-        </div>
-      );
-    }
-
-    let subdir = this.state.subdirectory === '' ? '' : '/' + this.state.subdirectory;
-    return (
-      <Div>
-        <Subtitle>Optionally edit default settings for this template</Subtitle>
-        <ValuesFormWrapper>
-          <ValuesForm
-            formData={this.context.currentModalData.template.Form}
-          />
-        </ValuesFormWrapper>
-        <RepoButton onClick={() => this.setState({ currentView: 'repo' })}>
-          <i className="material-icons">keyboard_backspace</i>
-          {this.state.selectedRepo.FullName + subdir}
-        </RepoButton>
-      </Div>
-    );
-  }
-
-  render() {
-    let { currentModalData } = this.context;
-    if (currentModalData) {
-      let { Name, Icon, Description } = currentModalData.template.Form;
-      let name = Name ? Name : currentModalData.template.Name;
-
-      return (
-        <StyledClusterConfigModal>
-          <CloseButton onClick={() => {
-            this.context.setCurrentModal(null, null);
-          }}>
-            <CloseButtonImg src={close} />
-          </CloseButton>
-
-          <ModalTitle>Launch Template</ModalTitle>
-          <ClusterSection>
-            <Template>
-              {Icon ? this.renderIcon(Icon) : this.renderIcon(currentModalData.template.Icon)}
-              {name}
-            </Template>
-            <i className="material-icons">arrow_right_alt</i>
-            <ClusterLabel>
-              <i className="material-icons">device_hub</i>Cluster
-            </ClusterLabel>
-            <Selector
-              activeValue={this.state.selectedCluster}
-              setActiveValue={(cluster: string) => this.setState({ selectedCluster: cluster })}
-              options={this.state.clusterOptions}
-              width='250px'
-              dropdownWidth='335px'
-              closeOverlay={true}
-            />
-          </ClusterSection>
-          {this.renderContents()}
-        </StyledClusterConfigModal>
-      );
-    }
-    return null;
-  }
-}
-
-LaunchTemplateModal.contextType = Context;
-
-const RepoButton = styled.div`
-  height: 40px;
-  font-size: 13px;
-  padding: 6px 20px 7px 13px;
-  border-radius: 5px;
-  background: #ffffff11;
-  color: #ffffff;
-  border: 1px solid #ffffff55;
-  cursor: pointer;
-  user-select: none;
-  display: flex;
-  align-items: center;
-  position: absolute;
-  bottom: 25px;
-  left: 30px;
-  :hover {
-    background: #ffffff22;
-  }
-
-  > i {
-    font-size: 16px;
-    margin-right: 10px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-  }
-`;
-
-const Div = styled.div`
-  width: calc(100% + 64px);
-  margin-left: -32px;
-  height: calc(100% - 50px);
-  position: relative;
-  padding: 0 32px;
-`;
-
-const ValuesFormWrapper = styled.div`
-  border: 1px solid #ffffff55;
-  border-radius: 3px;
-  width: 100%;
-  height: calc(100% - 149px);
-`;
-
-const ClusterLabel = styled.div`
-  margin-right: 10px;
-  display: flex;
-  align-items: center;
-  > i {
-    font-size: 16px;
-    margin-right: 6px;
-  }
-`;
-
-const Icon = styled.img`
-  width: 21px;
-  margin-right: 10px;
-`;
-
-
-const Polymer = styled.div`
-  margin-bottom: -3px;
-
-  > i {
-    color: ${props => props.theme.containerIcon};
-    font-size: 18px;
-    margin-right: 10px;
-  }
-`;
-
-const Template = styled.div`
-  display: flex;
-  align-items: center;
-  margin-right: 13px;
-`;
-
-const ClusterSection = styled.div`
-  display: flex;
-  align-items: center;
-  color: #ffffff;
-  font-family: 'Work Sans', sans-serif;
-  font-size: 14px;
-  font-weight: 500;
-  margin-top: 20px;
-
-  > i {
-    font-size: 25px;
-    color: #ffffff44;
-    margin-right: 13px;
-  }
-`;
-
-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 StyledClusterConfigModal= styled.div`
-  width: 100%;
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  padding: 25px 32px;
-  overflow: hidden;
-  border-radius: 6px;
-  background: #202227;
-`;

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

@@ -4,10 +4,11 @@ import styled from 'styled-components';
 import { Context } from '../../../../shared/Context';
 import api from '../../../../shared/api';
 
-import { PorterChart, RepoType, Cluster } from '../../../../shared/types';
+import { PorterChart, ChoiceType, Cluster } from '../../../../shared/types';
 import Selector from '../../../../components/Selector';
 import ImageSelector from '../../../../components/image-selector/ImageSelector';
 import TabRegion from '../../../../components/TabRegion';
+import ValuesForm from '../../../../components/values-form/ValuesForm';
 
 type PropsType = {
   currentTemplate: PorterChart,
@@ -19,25 +20,29 @@ type StateType = {
   clusterOptions: { label: string, value: string }[],
   selectedCluster: string,
   selectedImageUrl: string | null,
+  tabOptions: ChoiceType[],
+  tabContents: any
 };
 
-const tabOptions = [
-  { value: 'a', label: 'Tab A' },
-  { value: 'b', label: 'Tab B' },
-  { value: 'c', label: 'Tab C' },
-  { value: 'd', label: 'Tab D' },
-];
-
 export default class LaunchTemplate extends Component<PropsType, StateType> {
   state = {
     currentView: 'repo',
     clusterOptions: [] as { label: string, value: string }[],
     selectedCluster: this.context.currentCluster.name,
     selectedImageUrl: '',
+    tabOptions: [] as ChoiceType[],
+    tabContents: [] as any,
   };
 
   componentDidMount() {
-    console.log('current template: ', this.props.currentTemplate);
+    let tabOptions = [] as ChoiceType[];
+    let tabContents = [] as any;
+    this.props.currentTemplate.Form.Tabs.map((tab: any, i: number) => {
+      tabOptions.push({ value: tab.Name, label: tab.Label });
+      tabContents.push({ value: tab.Name, component: <ValuesForm sections={tab.Sections} /> });
+    });
+    this.setState({ tabOptions, tabContents });
+
     let { currentProject } = this.context;
 
     // TODO: query with selected filter once implemented
@@ -98,6 +103,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
         </ClusterSection>
 
         <Subtitle>Select the container image you would like to connect to this template.</Subtitle>
+        <Br />
         <ImageSelector
           selectedImageUrl={this.state.selectedImageUrl}
           setSelectedImageUrl={(x: string) => this.setState({ selectedImageUrl: x })}
@@ -107,13 +113,8 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
         <br />
         <Subtitle>Configure additional settings for this template (optional).</Subtitle>
         <TabRegion
-          options={tabOptions}
-          contents={[
-            { value: 'a', component: <h1>test</h1> },
-            { value: 'b', component: <h1>hello</h1> },
-            { value: 'c', component: <h1>world</h1> },
-            { value: 'd', component: <h1>hola</h1> },
-          ]}
+          options={this.state.tabOptions}
+          tabContents={this.state.tabContents}
         />
       </StyledLaunchTemplate>
     );
@@ -122,13 +123,16 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
 
 LaunchTemplate.contextType = Context;
 
+const Br = styled.div`
+  width: 100%;
+  height: 7px;
+`;
+
 const Subtitle = styled.div`
-  padding: 12px 0px 25px;
+  padding: 11px 0px 20px;
   font-family: 'Work Sans', sans-serif;
   font-size: 13px;
   color: #aaaabb;
-  margin-top: 3px;
-  margin-bottom: 5px;
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
@@ -220,4 +224,5 @@ const TitleSection = styled.div`
 
 const StyledLaunchTemplate = styled.div`
   width: 100%;
+  padding-bottom: 150px;
 `;

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

@@ -48,6 +48,17 @@ export default class TemplateInfo extends Component<PropsType, StateType> {
     return currentTemplate.Description;
   }
 
+  renderTagSection = () => {
+    if (this.props.currentTemplate.Form.Tags) {
+      return (
+        <TagSection>
+          <i className="material-icons">local_offer</i>
+          {this.renderTagList()}
+        </TagSection>
+      );
+    }
+  }
+
   render() {
     let { currentCluster } = this.context;
     let { Name, Icon } = this.props.currentTemplate.Form;
@@ -71,10 +82,7 @@ export default class TemplateInfo extends Component<PropsType, StateType> {
             Launch Template
           </Button>
         </TitleSection>
-        <TagSection>
-          <i className="material-icons">local_offer</i>
-          {this.renderTagList()}
-        </TagSection>
+        {this.renderTagSection()}
         <ContentSection>
           {this.renderMarkdown()}
         </ContentSection>

+ 11 - 2
dashboard/src/shared/types.tsx

@@ -76,8 +76,12 @@ export interface FormYAML {
 	Name?: string,  
 	Icon?: string,   
 	Description?: string,   
-	Tags?: string[],
-  Sections?: Section[]
+  Tags?: string[],
+  Tabs?: {
+    Name: string,
+    Label: string,
+    Sections?: Section[]
+  }[]
 }
 
 export interface Section {
@@ -118,4 +122,9 @@ export interface ProjectType {
     user_id: number,
     project_id: number
   }[]
+}
+
+export interface ChoiceType {
+  value: string,
+  label: string
 }