github.com/replicatedhq/ship@v0.55.0/web/init/src/components/kustomize/HelmValuesEditor.jsx (about)

     1  import React from "react";
     2  import * as linter from "replicated-lint";
     3  import Linter from "../shared/Linter";
     4  import AceEditor from "react-ace";
     5  import ErrorBoundary from "../../ErrorBoundary";
     6  import HelmAdvancedInput from "./HelmAdvancedInput";
     7  import get from "lodash/get";
     8  import find from "lodash/find";
     9  
    10  import "../../../node_modules/brace/mode/yaml";
    11  import "../../../node_modules/brace/theme/chrome";
    12  import "../../../node_modules/brace/ext/searchbox";
    13  
    14  export default class HelmValuesEditor extends React.Component {
    15    constructor(props) {
    16      super(props);
    17      this.state = {
    18        readOnly: false,
    19        showConsole: false,
    20        specErrors: [],
    21        specValue: "",
    22        initialSpecValue: "",
    23        helmLintErrors: [],
    24        saving: false,
    25        saveFinal: false,
    26        unsavedChanges: false,
    27        initialHelmReleaseName: "",
    28        initialHelmNamespace: "",
    29        helmReleaseName: "",
    30        displaySettings: false,
    31        helmNamespace: "",
    32      }
    33    }
    34  
    35    componentDidMount() {
    36      window.addEventListener("keydown", this.handleKeyboardSave);
    37      if (this.props.getStep.values) {
    38        this.setState({
    39          initialSpecValue: this.props.getStep.values,
    40          specValue: this.props.getStep.values,
    41          initialHelmReleaseName: this.props.getStep.helmName,
    42          initialHelmNamespace: this.props.getStep.namespace,
    43          helmReleaseName: this.props.getStep.helmName,
    44          helmNamespace: this.props.getStep.namespace,
    45        });
    46        this.helmEditor.editor.getSession().setValue(this.props.getStep.values); // this resets the UndoManager and prevents the editor from being able to be wiped out by too many CMD+Z's
    47      }
    48    }
    49  
    50  
    51  
    52    getLinterErrors = (specContents) => {
    53      if (specContents === "") return;
    54      const errors = new linter.Linter(specContents).lint();
    55      let markers = [];
    56      for (let error of errors) {
    57        if (error.positions) {
    58          for (let position of error.positions) {
    59            let line = get(position, "start.line");
    60            if (line) {
    61              markers.push({
    62                startRow: line,
    63                endRow: line + 1,
    64                className: "error-highlight",
    65                type: "background"
    66              })
    67            }
    68          }
    69        }
    70      }
    71      this.setState({
    72        specErrors: errors,
    73        specErrorMarkers: markers,
    74      });
    75    }
    76  
    77    onSpecChange = (value) => {
    78      const { initialSpecValue } = this.state;
    79      this.getLinterErrors(value);
    80      this.setState({
    81        specValue: value,
    82        unsavedChanges: !(initialSpecValue === value)
    83      });
    84    }
    85  
    86    handleContinue = () => {
    87      const { actions } = this.props;
    88      let submitAction;
    89      submitAction = find(actions, ["buttonType", "popover"]);
    90      this.props.handleAction(submitAction, true);
    91    }
    92  
    93    handleSkip = () => {
    94      const { initialSpecValue } = this.state;
    95      const payload = {
    96        values: initialSpecValue
    97      };
    98      this.setState({ helmLintErrors: [] });
    99      this.props.saveValues(payload)
   100        .then(({ errors }) => {
   101          if (errors) {
   102            return this.setState({
   103              saving: false, helmLintErrors: errors
   104            });
   105          }
   106          this.handleContinue();
   107        })
   108        .catch((err) => {
   109          // TODO: better handling
   110          console.log(err);
   111        })
   112    }
   113  
   114    handleSaveValues = (finalize) => {
   115      const { specValue, helmReleaseName, helmNamespace } = this.state;
   116      const payload = {
   117        values: specValue,
   118        releaseName: helmReleaseName,
   119        namespace: helmNamespace,
   120      };
   121      if (payload.values !== "") {
   122        this.setState({ saving: true, savedYaml: false, saveFinal: finalize, helmLintErrors: [] });
   123        this.props.saveValues(payload)
   124          .then(({ errors }) => {
   125            if (errors) {
   126              return this.setState({
   127                saving: false,
   128                helmLintErrors: errors,
   129                saveFinal: false,
   130              });
   131            }
   132            this.setState({ saving: false, savedYaml: true });
   133            if (finalize) {
   134              this.handleContinue();
   135            } else {
   136              setTimeout(() => {
   137                if (this.helmEditor) {
   138                  this.setState({ savedYaml: false });
   139                }
   140              }, 3000);
   141            }
   142          })
   143          .catch((err) => {
   144            // TODO: better handling
   145            console.log(err);
   146          })
   147      }
   148    }
   149  
   150    handleKeyboardSave = (e) => {
   151      const sKey = e.keyCode === 83;
   152      if (sKey && (e.ctrlKey || e.metaKey)) {
   153        e.preventDefault();
   154        e.stopPropagation();
   155        this.handleSaveValues(false);
   156      }
   157    }
   158  
   159    componentWillUnmount() {
   160      window.removeEventListener("keydown", this.handleKeyboardSave);
   161    }
   162  
   163    handleOnChangehelmReleaseName = (helmReleaseName) => this.setState({ helmReleaseName })
   164  
   165    handleOnChangehelmNamespace = (helmNamespace) => this.setState({ helmNamespace })
   166  
   167    render() {
   168      const {
   169        readOnly,
   170        specValue,
   171        initialSpecValue,
   172        saving,
   173        saveFinal,
   174        savedYaml,
   175        helmLintErrors,
   176        initialHelmReleaseName,
   177        helmReleaseName,
   178        displaySettings,
   179        helmNamespace,
   180        initialHelmNamespace,
   181      } = this.state;
   182      const {
   183        values,
   184        readme,
   185        name
   186      } = this.props.shipAppMetadata;
   187      const { firstRoute, goBack } = this.props;
   188      return (
   189        <ErrorBoundary>
   190          <div className="flex-column flex1 HelmValues--wrapper u-paddingTop--30">
   191            <div className="flex-column flex-1-auto u-overflow--auto container">
   192              <p className="u-color--dutyGray u-fontStize--large u-fontWeight--medium u-marginBottom--small">
   193              /{name}/
   194                <span className="u-color--tuna u-fontWeight--bold">values.yaml</span>
   195              </p>
   196              <p className="u-color--dustyGray u-fontSize--normal u-marginTop--normal u-marginBottom--20">Here you can edit the values.yaml to specify values for your application. You will be able to apply overlays for your YAML in the next step.</p>
   197              <div className="advanced-settings-wrapper u-marginTop--10">
   198                <div className={`section-border ${displaySettings ? "open" : "closed"} flex justifyContent--center u-position--relative`}>
   199                  <p className="flex-auto u-fontSize--small u-color--tundora u-fontWeight--medium u-cursor--pointer" onClick={() => { this.setState({ displaySettings: !this.state.displaySettings }) }}>{displaySettings ? "Close Advanced Settings" : "Show Advanced Settings"}</p>
   200                </div>
   201                {displaySettings ? <div className="settings u-marginBottom--20">
   202                  <HelmAdvancedInput
   203                    value={helmReleaseName}
   204                    onChange={this.handleOnChangehelmReleaseName}
   205                    title="Helm Name"
   206                    subTitle="This is the name that will be used to template your Helm chart."
   207                  />
   208                  <HelmAdvancedInput
   209                    value={helmNamespace}
   210                    onChange={this.handleOnChangehelmNamespace}
   211                    title="Helm Namespace"
   212                    subTitle="This is the namespace that will be used to template your Helm chart."
   213                    placeholder="default"
   214                  />
   215                </div> : null}
   216              </div>
   217              <div className="AceEditor--wrapper helm-values flex1 flex u-height--full u-width--full">
   218                <div className="flex1 flex-column u-width--half">
   219                  <AceEditor
   220                    ref={(editor) => { this.helmEditor = editor }}
   221                    mode="yaml"
   222                    theme="chrome"
   223                    className={`${readOnly ? "disabled-ace-editor ace-chrome" : ""}`}
   224                    readOnly={readOnly}
   225                    onChange={this.onSpecChange}
   226                    markers={this.state.specErrorMarkers}
   227                    value={specValue}
   228                    height="100%"
   229                    width="100%"
   230                    editorProps={{
   231                      $blockScrolling: Infinity,
   232                      useSoftTabs: true,
   233                      tabSize: 2,
   234                    }}
   235                    setOptions={{
   236                      scrollPastEnd: true
   237                    }}
   238                  />
   239                </div>
   240                <div className={`flex-auto flex-column console-wrapper u-width--third ${!this.state.showConsole ? "visible" : ""}`}>
   241                  <Linter errors={this.state.specErrors} spec={values} previewEnabled={true} readme={this.props.getStep.readme || readme} />
   242                </div>
   243              </div>
   244            </div>
   245            <div className="actions-wrapper container u-width--full flex flex-auto">
   246              {firstRoute ? null :
   247                <div className="flex-auto u-marginRight--normal">
   248                  <button className="btn secondary" onClick={() => goBack()}>Back</button>
   249                </div>
   250              }
   251              <div className="flex flex1 alignItems--center justifyContent--flexEnd">
   252                {initialSpecValue === specValue && initialHelmReleaseName === helmReleaseName && helmNamespace === initialHelmNamespace ?
   253                  <button className="btn primary" onClick={() => { this.handleSkip() }}>Continue</button>
   254                  :
   255                  <div className="flex">
   256                    <div className="flex1 flex-column flex-verticalCenter">
   257                      {helmLintErrors.length ?
   258                        <p className="u-color--chestnut u-fontSize--small u-fontWeight--medium u-marginRight--normal u-lineHeight--normal">{helmLintErrors.join("\n")}</p>
   259                      : null}
   260                      {savedYaml ?
   261                        <p className="u-color--vidaLoca u-fontSize--small u-fontWeight--medium u-marginRight--normal u-lineHeight--normal">Values saved</p>
   262                      : null}
   263                    </div>
   264                    <button className="btn primary hv-save u-marginRight--normal" onClick={() => this.handleSaveValues(false)} disabled={saving || saveFinal}>{saving ? "Saving" : "Save values"}</button>
   265                    <button className="btn secondary hv-save" onClick={() => this.handleSaveValues(true)} disabled={saving || saveFinal}>{saveFinal ? "Saving values" : "Save & continue"}</button>
   266                  </div>
   267                }
   268              </div>
   269            </div>
   270  
   271          </div>
   272        </ErrorBoundary>
   273      );
   274    }
   275  }