jusrhee 5 лет назад
Родитель
Сommit
d9eaf9a58c

+ 32 - 0
dashboard/package-lock.json

@@ -396,6 +396,11 @@
         "jest-diff": "^24.3.0"
       }
     },
+    "@types/js-yaml": {
+      "version": "3.12.5",
+      "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.5.tgz",
+      "integrity": "sha512-JCcp6J0GV66Y4ZMDAQCXot4xprYB+Zfd3meK9+INSJeVZwJmHAW30BBEEkPzXswMXuiyReUGOP3GxrADc9wPww=="
+    },
     "@types/json-schema": {
       "version": "7.0.6",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
@@ -972,6 +977,14 @@
       "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
       "dev": true
     },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
     "aria-query": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
@@ -2490,6 +2503,11 @@
         "estraverse": "^4.1.1"
       }
     },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+    },
     "esrecurse": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@@ -4128,6 +4146,15 @@
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
       "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
     },
+    "js-yaml": {
+      "version": "3.14.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
+      "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
     "jsesc": {
       "version": "2.5.2",
       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@@ -6188,6 +6215,11 @@
         "extend-shallow": "^3.0.0"
       }
     },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+    },
     "ssri": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",

+ 2 - 0
dashboard/package.json

@@ -3,10 +3,12 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@types/js-yaml": "^3.12.5",
     "@types/qs": "^6.9.5",
     "ace-builds": "^1.4.12",
     "axios": "^0.20.0",
     "dotenv": "^8.2.0",
+    "js-yaml": "^3.14.0",
     "qs": "^6.9.4",
     "react": "^16.13.1",
     "react-ace": "^9.1.3",

+ 1 - 0
dashboard/src/components/TabSelector.tsx

@@ -37,6 +37,7 @@ export default class TabSelector extends Component<PropsType, StateType> {
       this.props.options.map((option: selectOption, i: number) => {
         return (
           <Tab
+            key={i}
             onClick={() => this.handleTabClick(option.value)}
             tabWidth={this.props.tabWidth}
           >

+ 14 - 4
dashboard/src/components/YamlEditor.tsx

@@ -8,7 +8,8 @@ import 'ace-builds/src-noconflict/theme-terminal';
 type PropsType = {
   value: string,
   onChange: (e: any) => void,
-  height?: string
+  height?: string,
+  border?: boolean
 }
 
 type StateType = {
@@ -41,7 +42,10 @@ class YamlEditor extends Component<PropsType, StateType> {
   render() {
     return (
       <Holder>
-        <Editor onSubmit={this.handleSubmit}>
+        <Editor
+          onSubmit={this.handleSubmit}
+          border={this.props.border}
+        >
           <AceEditor
             mode='yaml'
             value={this.props.value}
@@ -62,12 +66,18 @@ class YamlEditor extends Component<PropsType, StateType> {
 export default YamlEditor;
 
 const Editor = styled.form`
-  border-radius: 5px;
-  border: 1px solid #ffffff22;
+  border-radius: ${(props: { border: boolean }) => props.border ? '5px' : ''};
+  border: ${(props: { border: boolean }) => props.border ? '1px solid #ffffff22' : ''};
 `;
 
 const Holder = styled.div`
   .ace_scrollbar {
     display: none;
   }
+  .ace_editor, .ace_editor * {
+    font-family: "Monaco", "Menlo", "Ubuntu Mono", "Droid Sans Mono", "Consolas", monospace !important;
+    font-size: 12px !important;
+    font-weight: 400 !important;
+    letter-spacing: 0 !important;
+  }
 `;

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

@@ -49,6 +49,8 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
   }
 
   renderTabContents = () => {
+    let { currentChart, refreshChart } = this.props;
+
     if (this.state.currentTab === 'overview') {
       return (
         <Wrapper>
@@ -57,7 +59,12 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
       );
     }
 
-    return <ValuesYaml />
+    return (
+      <ValuesYaml
+        currentChart={currentChart}
+        refreshChart={refreshChart}
+      />
+    );
   }
 
   render() {
@@ -142,6 +149,7 @@ const ContentSection = styled.div`
   justify-content: center;
   align-items: center;
   font-size: 13px;
+  overflow-y: auto;
 `;
 
 const StatusColor = styled.div`

+ 11 - 3
dashboard/src/main/home/dashboard/expanded-chart/RevisionSection.tsx

@@ -47,6 +47,13 @@ export default class RevisionSection extends Component<PropsType, StateType> {
     this.refreshHistory();
   }
 
+  // Handle update of values.yaml
+  componentDidUpdate(prevProps: PropsType) {
+    if (this.props.chart !== prevProps.chart) {
+      this.refreshHistory();
+    }
+  }
+
   readableDate = (s: string) => {
     let ts = new Date(s);
     let date = ts.toLocaleDateString();
@@ -61,10 +68,10 @@ export default class RevisionSection extends Component<PropsType, StateType> {
     api.rollbackChart('<token>', {
       namespace: this.props.chart.namespace,
       context: this.context.currentCluster,
-      storage: 'secret'
-    }, {
-      name: this.props.chart.name,
+      storage: 'secret',
       revision: revisionNumber
+    }, {
+      name: this.props.chart.name
     }, (err: any, res: any) => {
       if (err) {
         console.log(err)
@@ -301,6 +308,7 @@ const RevisionsTable = styled.table`
   margin-top: 5px;
   padding-left: 32px;
   padding-bottom: 20px;
+  min-width: 500px;
 `;
 
 const Revision = styled.div`

+ 69 - 5
dashboard/src/main/home/dashboard/expanded-chart/ValuesYaml.tsx

@@ -1,32 +1,96 @@
 import React, { Component } from 'react';
 import styled from 'styled-components';
+import yaml from 'js-yaml';
+
+import { ChartType } from '../../../../shared/types';
+import api from '../../../../shared/api';
+import { Context } from '../../../../shared/Context';
 
 import YamlEditor from '../../../../components/YamlEditor';
+import SaveButton from '../../../../components/SaveButton';
 
 type PropsType = {
+  currentChart: ChartType
+  refreshChart: () => void
 };
 
 type StateType = {
-  valuesYaml: string,
+  values: string,
+  saveValuesStatus: string | null
 };
 
 export default class ValuesYaml extends Component<PropsType, StateType> {
   state = {
-    valuesYaml: '# placeholder for values.yaml'
+    values: '',
+    saveValuesStatus: null as (string | null)
+  }
+
+  updateValues() {
+    let values = yaml.dump(this.props.currentChart.config);
+    this.setState({ values });
+  }
+
+  componentDidMount() {
+    this.updateValues();
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if (this.props.currentChart !== prevProps.currentChart) {
+      this.updateValues();
+    }
+  }
+
+  handleSaveValues = () => {
+    let { currentCluster } = this.context;
+    this.setState({ saveValuesStatus: 'loading' });
+
+    api.upgradeChartValues('<token>', {
+      namespace: this.props.currentChart.namespace,
+      context: currentCluster,
+      storage: 'secret',
+      values: this.state.values
+    }, { name: this.props.currentChart.name }, (err: any, res: any) => {
+      if (err) {
+        console.log(err)
+        this.setState({ saveValuesStatus: 'error ' });
+      } else {
+        this.setState({ saveValuesStatus: 'successful' });
+        this.props.refreshChart();
+      }
+    });
   }
 
   render() {
     return (
       <StyledValuesYaml>
-        <YamlEditor
-          value={this.state.valuesYaml}
-          onChange={(e: any) => this.setState({ valuesYaml: e })}
+        <Wrapper>
+          <YamlEditor
+            value={this.state.values}
+            onChange={(e: any) => this.setState({ values: e })}
+          />
+        </Wrapper>
+        <SaveButton
+          text='Update Values'
+          onClick={this.handleSaveValues}
+          status={this.state.saveValuesStatus}
         />
       </StyledValuesYaml>
     );
   }
 }
 
+ValuesYaml.contextType = Context;
+
+const Wrapper = styled.div`
+  overflow: auto;
+  height: calc(100% - 60px);
+  border-radius: 5px;
+  border: 1px solid #ffffff22;
+`;
+
 const StyledValuesYaml = styled.div`
+  display: flex;
+  flex-direction: column;
   width: 100%;
+  height: 100%;
 `;

+ 2 - 1
dashboard/src/main/home/modals/ClusterConfigModal.tsx

@@ -161,6 +161,7 @@ export default class ClusterConfigModal extends Component<PropsType, StateType>
             value={this.state.rawKubeconfig}
             onChange={(e: any) => this.setState({ rawKubeconfig: e })}
             height='295px'
+            border={true}
           />
           <UploadButton>
             <i className="material-icons">cloud_upload</i> Upload Kubeconfig
@@ -315,7 +316,7 @@ const Subtitle = styled.div`
   padding: 15px 0px;
   font-family: 'Work Sans', sans-serif;
   font-size: 13px;
-  color: #aaa;
+  color: #aaaabb;
   margin-top: 8px;
   overflow: hidden;
   white-space: nowrap;

+ 18 - 7
dashboard/src/shared/api.tsx

@@ -48,14 +48,14 @@ const getCharts = baseApi<{
   skip: number,
   byDate: boolean,
   statusFilter: string[]
-}>('GET', '/api/charts');
+}>('GET', '/api/releases');
 
 const getChart = baseApi<{
   namespace: string,
   context: string,
   storage: string
 }, { name: string, revision: number }>('GET', pathParams => {
-  return `/api/charts/${pathParams.name}/${pathParams.revision}`;
+  return `/api/releases/${pathParams.name}/${pathParams.revision}`;
 });
 
 const getNamespaces = baseApi<{
@@ -67,15 +67,25 @@ const getRevisions = baseApi<{
   context: string,
   storage: string
 }, { name: string }>('GET', pathParams => {
-  return `/api/charts/${pathParams.name}/history`;
+  return `/api/releases/${pathParams.name}/history`;
 });
 
 const rollbackChart = baseApi<{
   namespace: string,
   context: string,
-  storage: string
-}, { name: string, revision: number }>('POST', pathParams => {
-  return `/api/charts/rollback/${pathParams.name}/${pathParams.revision}`;
+  storage: string,
+  revision: number
+}, { name: string }>('POST', pathParams => {
+  return `/api/releases/${pathParams.name}/rollback`;
+});
+
+const upgradeChartValues = baseApi<{
+  namespace: string,
+  context: string,
+  storage: string,
+  values: string
+}, { name: string }>('POST', pathParams => {
+  return `/api/releases/${pathParams.name}/upgrade`;
 });
 
 // Bundle export to allow default api import (api.<method> is more readable)
@@ -91,5 +101,6 @@ export default {
   getChart,
   getNamespaces,
   getRevisions,
-  rollbackChart
+  rollbackChart,
+  upgradeChartValues
 }

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

@@ -25,6 +25,7 @@ export interface ChartType {
       apiVersion: string
     },
   },
+  config: string,
   version: number,
   namespace: string
 }