github.com/replicatedhq/ship@v0.55.0/web/init/src/components/config_only/ConfigOnly.jsx (about) 1 import React from "react"; 2 import ErrorBoundary from "../../ErrorBoundary"; 3 import each from 'lodash/each'; 4 import isEmpty from 'lodash/isEmpty'; 5 import map from 'lodash/map'; 6 import partialRight from 'lodash/partialRight'; 7 import omit from 'lodash/omit'; 8 9 import { ConfigService } from "../../services/ConfigService"; 10 11 import Layout from "../../Layout"; 12 import ConfigRender from "../config_render/ConfigRender"; 13 import Toast from "../shared/Toast"; 14 import Loader from "../shared/Loader"; 15 16 const EDITABLE_ITEM_TYPES = [ 17 "select", "text", "textarea", "password", "file", "bool", "select_many", "select_one", 18 ]; 19 20 export default class ConfigOnly extends React.Component { 21 22 constructor(props) { 23 super(props); 24 this.state = { 25 toastDetails: { 26 opts: {} 27 }, 28 }; 29 } 30 31 componentDidMount() { 32 if (!this.props.settingsFieldsList.length) { 33 this.props.getApplicationSettings({item_values: null}); 34 } 35 } 36 37 componentDidUpdate(lastProps) { 38 if (this.props.phase !== lastProps.phase) { 39 if (this.props.phase !== "render.config") { 40 this.props.history.push("/"); 41 } 42 } 43 if (this.props.settingsFields !== lastProps.settingsFields) { 44 const data = this.getData(this.props.settingsFields); 45 this.setState({ itemData: data }); 46 } 47 } 48 49 getData = (groups) => { 50 const getItemData = (item) => { 51 let data = { 52 name: item.name, 53 value: item.value, 54 multi_value: item.multi_value, 55 }; 56 if (item.multiple) { 57 if (item.multi_value && item.multi_value.length) { 58 data.multi_value = item.multi_value; 59 } else if (item.default) { 60 data.multi_value = [item.default]; 61 } 62 } else { 63 if (item.value && item.value.length) { 64 data.value = item.value; 65 } else { 66 data.value = item.default; 67 } 68 } 69 if (item.type === "file") { 70 data.data = ""; 71 if (item.multiple) { 72 if (item.multi_data && item.multi_data.length) { 73 data.multi_data = item.multi_data; 74 } else { 75 data.multi_data = []; 76 } 77 } else { 78 data.data = item.data; 79 } 80 } 81 return data; 82 }; 83 84 let data = []; 85 each(groups, (group) => { 86 if (ConfigService.isEnabled(groups, group)) { 87 each(group.items, (item) => { 88 if (ConfigService.isEnabled(groups, item)) { 89 if (item.type !== "select_many") { 90 data.push(getItemData(item)); 91 } 92 if (item.type !== "select_one") { 93 each(item.items, (childItem) => { 94 data.push(getItemData(childItem)); 95 }); 96 } 97 } 98 }); 99 } 100 }); 101 return data; 102 } 103 104 cancelToast = () => { 105 let nextState = {}; 106 nextState.toastDetails = { 107 showToast: false, 108 title: "", 109 subText: "", 110 type: "default", 111 opts: {} 112 }; 113 this.setState(nextState) 114 } 115 116 onConfigSaved = () => { 117 const { 118 actions, 119 handleAction, 120 finalizeApplicationSettings, 121 } = this.props; 122 const configAction = actions[0]; 123 const { text } = configAction; 124 125 const nextState = { 126 toastDetails: { 127 showToast: true, 128 title: "All changes have been saved.", 129 type: "default", 130 opts: { 131 showCancelButton: true, 132 confirmButtonText: text, 133 confirmAction: async () => { 134 await finalizeApplicationSettings(this.state.itemData, false).catch(); 135 await handleAction(configAction, true); 136 }, 137 }, 138 }, 139 }; 140 141 this.setState(nextState); 142 } 143 144 onConfigError = () => { 145 const { configErrors } = this.props; 146 if (!configErrors.length) return; 147 configErrors.map((err) => { 148 // TODO: Refactor to pass errors down instead of manipulating 149 // the DOM 150 const el = document.getElementById(`${err.fieldName}-errblock`); 151 if (el) { 152 el.innerHTML = err.message; 153 el.classList.add("visible"); 154 } 155 }) 156 } 157 158 handleConfigSave = async (e, nextStep = false) => { 159 let errs = document.getElementsByClassName("config-errblock"); 160 for (let i = 0; i < errs.length; i++) { 161 errs[i].classList.remove("visible"); 162 } 163 await this.props.saveApplicationSettings(this.state.itemData, false) 164 .then((response) => { 165 if (!response) { 166 this.onConfigError(); 167 return; 168 } 169 this.onConfigSaved(); 170 if(nextStep) this.state.toastDetails.opts.confirmAction(); 171 }) 172 .catch() 173 } 174 175 pingServer = async (data) => { 176 const itemValues = map(data, partialRight(omit, "data", "multi_data")); 177 await this.props.getApplicationSettings({item_values: itemValues}, false) 178 .then() 179 .catch(); 180 } 181 182 handleConfigChange = (data) => { 183 this.setState({ itemData: data }); 184 this.pingServer(data); 185 } 186 187 render() { 188 const { 189 dataLoading, 190 settingsFields, 191 settingsFieldsList, 192 routeId, 193 goBack, 194 firstRoute 195 } = this.props; 196 const { toastDetails } = this.state; 197 198 return ( 199 <Layout configOnly={true} configRouteId={routeId}> 200 <ErrorBoundary className="flex-column"> 201 <div className="flex-column flex1"> 202 <div className="flex-column flex1 u-overflow--hidden u-position--relative"> 203 <Toast toast={toastDetails} onCancel={this.cancelToast} /> 204 <div className="flex-1-auto flex-column u-overflow--auto container u-paddingTop--30"> 205 {dataLoading.appSettingsFieldsLoading || isEmpty(settingsFields) ? 206 <div className="flex1 flex-column justifyContent--center alignItems--center"> 207 <Loader size="60" /> 208 </div> 209 : 210 <ConfigRender 211 fieldsList={settingsFieldsList} 212 fields={settingsFields} 213 handleChange={this.handleConfigChange} 214 getData={this.getData} 215 /> 216 } 217 </div> 218 <div className="flex-auto flex layout-footer-actions"> 219 <div className="flex flex1"> 220 {firstRoute ? null : 221 <div className="flex-auto u-marginRight--normal"> 222 <button className="btn secondary" onClick={() => goBack()}>Back</button> 223 </div> 224 } 225 <div className="flex-column flex-verticalCenter"> 226 <p className="u-margin--none u-marginRight--30 u-fontSize--small u-color--dustyGray u-fontWeight--normal">Contributed by <a target="_blank" rel="noopener noreferrer" href="https://replicated.com" className="u-fontWeight--medium u-color--astral u-textDecoration--underlineOnHover">Replicated</a></p> 227 </div> 228 </div> 229 <div className="flex flex1 justifyContent--flexEnd"> 230 <button type="button" disabled={dataLoading.saveAppSettingsLoading} onClick={this.handleConfigSave} className="btn secondary u-marginRight--10">{dataLoading.saveAppSettingsLoading ? "Saving" : "Save changes"}</button> 231 <button type="button" disabled={dataLoading.saveAppSettingsLoading} onClick={(e) => this.handleConfigSave(e, true)} className="btn primary">Save and continue to next step</button> 232 </div> 233 </div> 234 </div> 235 </div> 236 </ErrorBoundary> 237 </Layout> 238 ); 239 } 240 }