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 }