AddCloudConnection.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. /* eslint-disable dot-notation */
  15. import React, { PropTypes } from 'react';
  16. import withStyles from 'isomorphic-style-loader/lib/withStyles';
  17. import s from './AddCloudConnection.scss';
  18. import Reflux from 'reflux';
  19. import Helper from "../Helper"
  20. import ConnectionsStore from '../../stores/ConnectionsStore';
  21. import ConnectionsActions from '../../actions/ConnectionsActions';
  22. import NotificationActions from '../../actions/NotificationActions';
  23. import Dropdown from '../NewDropdown';
  24. import Switch from '../Switch'
  25. import LoadingIcon from "../LoadingIcon/LoadingIcon";
  26. const title = 'Add Cloud Connection';
  27. const endpointStatuses = { IDLE: 0, VALIDATING: 1, ERROR: 2, SUCCESS: 3 }
  28. const submissionTypes = { ADD: 0, EDIT: 1 }
  29. class AddCloudConnection extends Reflux.Component {
  30. static contextTypes = {
  31. onSetTitle: PropTypes.func.isRequired,
  32. };
  33. static defaultProps = {
  34. cloud: null,
  35. connection: null,
  36. type: "new",
  37. onResizeUpdate: () => {}
  38. }
  39. constructor(props) {
  40. super(props)
  41. this.store = ConnectionsStore
  42. this.state = {
  43. submissionType: submissionTypes.ADD,
  44. endpointStatus: endpointStatuses.IDLE,
  45. showErrorMessage: false,
  46. type: props.type, // type of operation: new/edit
  47. connection: props.connection, // connection object (on edit)
  48. connectionName: "", // connection name field
  49. description: "", // connection description field
  50. currentCloud: this.props.cloud, // chosen cloud - if adding a new endpoint
  51. currentCloudData: null, // endpoint field data
  52. requiredFields: [], // array that holds all the endpoint required fields - used for field validation
  53. cloudFormsSubmitted: false // flag that indicates if the form has been submitted - used for field validation
  54. }
  55. }
  56. componentWillMount() {
  57. super.componentWillMount.call(this)
  58. this.componentWillUnmount = false
  59. this.context.onSetTitle(title);
  60. if (this.state.currentCloudData == null) {
  61. this.setState({ currentCloudData: {} })
  62. }
  63. this.setState({ submissionType: this.props.type === 'new' ? submissionTypes.ADD : submissionTypes.EDIT })
  64. }
  65. componentWillUnmount() {
  66. super.componentWillUnmount.call(this)
  67. this.componentWillUnmount = true
  68. clearTimeout(this.closeTimeout)
  69. }
  70. componentDidMount() {
  71. if (this.state.connection) {
  72. this.state.allClouds.forEach(item => {
  73. if (item.name === this.state.connection.type) {
  74. let credentials = this.state.connection.credentials
  75. let newCredentials = {}
  76. for (let i in credentials) {
  77. if (typeof credentials[i] == "object") {
  78. newCredentials['login_type'] = i
  79. // credentials['user_credentials'] = {}
  80. for (let j in credentials[i]) {
  81. // credentials['user_credentials'][j] = credentials[i][j]
  82. newCredentials[j] = credentials[i][j]
  83. }
  84. } else if (typeof credentials[i] === 'boolean') {
  85. newCredentials[i] = credentials[i]
  86. } else {
  87. newCredentials[i] = credentials[i] + ""
  88. }
  89. }
  90. this.setState({
  91. currentCloudData: newCredentials,
  92. connectionName: this.state.connection.name,
  93. description: this.state.connection.description
  94. }, () => {
  95. this.chooseCloud(item)
  96. })
  97. }
  98. })
  99. } else if (this.props.cloud) {
  100. this.chooseCloud(this.props.cloud)
  101. }
  102. // Fixes an issue with focus when multiple modals are rendered and escape key is not captured.
  103. // Test with adding cloud connection from wizard.
  104. setTimeout(() => { this.rootDiv.focus() }, 100)
  105. }
  106. /**
  107. * Function called upon saving an endpoint - handles both new and edit operations
  108. */
  109. handleSave() {
  110. let valid = true
  111. let requiredFields = this.state.requiredFields
  112. for (let i in this.state.currentCloudData) {
  113. if (requiredFields.indexOf(i) > -1 && !this.state.currentCloudData[i]) {
  114. valid = false
  115. }
  116. }
  117. requiredFields.forEach((field) => {
  118. if (!this.state.currentCloudData[field]) {
  119. valid = false
  120. }
  121. })
  122. if (this.state.connectionName.trim().length == 0) {
  123. valid = false
  124. }
  125. if (!valid) {
  126. NotificationActions.notify("Please fill all required fields", "error")
  127. this.setState({ cloudFormsSubmitted: true })
  128. } else {
  129. let credentials = Object.assign({}, this.state.currentCloudData)
  130. for (let key in credentials) {
  131. if (credentials[key].label) {
  132. credentials[key] = credentials[key].value
  133. }
  134. let field = this.state.currentCloud.endpoint.fields.find(function findByName(f) { return f.name == this }, key);
  135. if (!field || !field.dataType) {
  136. continue;
  137. }
  138. // Convert datatype
  139. switch (field.dataType) {
  140. case 'boolean':
  141. credentials[key] = (credentials[key] === true ||
  142. ((typeof credentials[key] === 'string' || credentials[key] instanceof String) &&
  143. credentials[key].toLowerCase() == "true"));
  144. break;
  145. case 'integer':
  146. let value = parseInt(credentials[key], 10);
  147. if (value.toString() != credentials[key]) {
  148. valid = false;
  149. NotificationActions.notify('"' + key + '" needs to be an integer', 'error');
  150. }
  151. credentials[key] = value;
  152. break;
  153. default:
  154. // retain original value
  155. break;
  156. }
  157. }
  158. if (!valid) {
  159. return;
  160. }
  161. // If there's a switch radio, create a hierarchical structure with the selected radio as the root.
  162. this.state.currentCloud.endpoint.fields.forEach(field => {
  163. if (field.type === 'switch-radio') {
  164. credentials[credentials[field.name]] = {}
  165. field.options.forEach(fieldOptions => {
  166. if (fieldOptions.value === credentials[field.name]) {
  167. fieldOptions.fields.forEach(fieldOptionField => {
  168. credentials[credentials[field.name]][fieldOptionField.name] = credentials[fieldOptionField.name]
  169. })
  170. }
  171. })
  172. }
  173. })
  174. // If endpoint is new
  175. if (this.state.type === 'new') {
  176. this.setState({ type: 'edit' })
  177. ConnectionsActions.newEndpoint({
  178. name: this.state.connectionName,
  179. description: this.state.description,
  180. type: this.state.currentCloud.name,
  181. connection_info: credentials
  182. }, (response) => {
  183. this.validateEndpoint(response.data.endpoint)
  184. if (this.props.onConnectionAdded) {
  185. this.props.onConnectionAdded(response.data.endpoint)
  186. }
  187. })
  188. this.setState({ endpointStatus: endpointStatuses.VALIDATING })
  189. } else { // If editing an endpoint
  190. ConnectionsActions.editEndpoint(this.state.connection, {
  191. name: this.state.connectionName,
  192. description: this.state.description,
  193. connection_info: credentials
  194. }, (response) => {
  195. this.validateEndpoint(response.data.endpoint)
  196. if (this.props.onConnectionAdded && this.submissionType === submissionTypes.ADD) {
  197. this.props.onConnectionAdded(response.data.endpoint)
  198. }
  199. })
  200. this.setState({ endpointStatus: endpointStatuses.VALIDATING })
  201. }
  202. }
  203. }
  204. validateEndpoint(endpoint) {
  205. if (this.componentWillUnmount && this.state.submissionType === submissionTypes.ADD) {
  206. ConnectionsActions.deleteConnection(endpoint)
  207. return
  208. }
  209. this.setState({ connection: endpoint })
  210. ConnectionsActions.validateConnection(endpoint, response => {
  211. let validation = response.data["validate-connection"]
  212. if (validation.valid) {
  213. this.setState({ endpointStatus: endpointStatuses.SUCCESS })
  214. } else {
  215. this.setState({
  216. endpointStatus: endpointStatuses.ERROR,
  217. errorMessage: validation.message
  218. })
  219. }
  220. }, () => {
  221. this.setState({ endpointStatus: endpointStatuses.ERROR })
  222. })
  223. }
  224. /**
  225. * Handles change `name` property
  226. * @param e
  227. */
  228. handleChangeName(e) {
  229. this.setState({ connectionName: e.target.value })
  230. }
  231. /**
  232. * Handles change `description` property
  233. * @param e
  234. */
  235. handleChangeDescription(e) {
  236. this.setState({ description: e.target.value })
  237. }
  238. /**
  239. * Handler to choose the cloud which the endpoint will be assigned to
  240. * @param cloud
  241. */
  242. chooseCloud(cloud) {
  243. let currentCloudData = {}
  244. if (this.state.currentCloudData !== null) {
  245. currentCloudData = this.state.currentCloudData
  246. }
  247. let requiredFields = []
  248. cloud.endpoint.fields.forEach(field => {
  249. if (typeof currentCloudData[field.name] == "undefined") {
  250. if (typeof field.defaultValue === 'undefined') {
  251. currentCloudData[field.name] = "";
  252. } else {
  253. currentCloudData[field.name] = field.defaultValue.toString();
  254. }
  255. }
  256. if (field.required) {
  257. requiredFields.push(field.name)
  258. }
  259. })
  260. this.props.onResizeUpdate()
  261. this.setState({
  262. currentCloud: cloud,
  263. currentCloudData: currentCloudData,
  264. requiredFields: requiredFields
  265. }, this.setDefaultValues)
  266. }
  267. /**
  268. * Function that goes back from endpoint validation to edit mode
  269. */
  270. backToEdit() {
  271. this.props.onResizeUpdate()
  272. this.setState({ validateEndpoint: null })
  273. }
  274. /**
  275. * Handles back operation when adding a new endpoint and want to switch cloud. Resets all previous cloud data.
  276. */
  277. handleBack() {
  278. this.props.onResizeUpdate()
  279. this.setState({
  280. currentCloudData: null,
  281. currentCloud: null,
  282. requiredFields: null,
  283. connectionName: "",
  284. description: null
  285. })
  286. }
  287. /**
  288. * Handles cancel edit/add endpoint
  289. */
  290. handleCancel() {
  291. if (this.state.submissionType === submissionTypes.ADD && this.state.connection && this.state.connection.id) {
  292. ConnectionsActions.deleteConnection(this.state.connection)
  293. }
  294. this.props.closeHandle();
  295. }
  296. handleClose() {
  297. this.props.closeHandle();
  298. }
  299. /**
  300. * Sets default values for cloud fields
  301. */
  302. setDefaultValues() {
  303. this.state.currentCloud.endpoint.fields.forEach(field => {
  304. let currentCloudData = this.state.currentCloudData
  305. switch (field.type) {
  306. case 'switch':
  307. if (field.default && typeof currentCloudData[field.name] == "undefined") {
  308. currentCloudData[field.name] = field.default
  309. this.setState({ currentCloudData: currentCloudData })
  310. }
  311. break
  312. case 'switch-radio':
  313. field.options.forEach(option => {
  314. if (option.default && !currentCloudData[field.name]) {
  315. currentCloudData[field.name] = option.value
  316. this.setRadioRequiredFields(field, option.value)
  317. this.setState({ currentCloudData: currentCloudData })
  318. }
  319. }, this)
  320. break;
  321. case 'text':
  322. if (field.default && typeof currentCloudData[field.name] == "undefined") {
  323. currentCloudData[field.name] = field.default
  324. this.setState({ currentCloudData: currentCloudData })
  325. }
  326. break
  327. default:
  328. break;
  329. }
  330. }, this)
  331. }
  332. /**
  333. * Checks whether the field is valid. Only goes through validation if field is required
  334. * @param field
  335. * @returns {boolean}
  336. */
  337. isValid(field) {
  338. if (field.required && this.state.cloudFormsSubmitted) {
  339. if (this.state.currentCloudData[field.name]) {
  340. return !(this.state.currentCloudData[field.name] && this.state.currentCloudData[field.name].length == 0);
  341. } else {
  342. return false
  343. }
  344. } else {
  345. return true
  346. }
  347. }
  348. /**
  349. * Dinamically change the required fields affected by the current radio selection
  350. * @param field
  351. * @param currentValue
  352. */
  353. setRadioRequiredFields(field, currentValue) {
  354. let requiredFields = this.state.requiredFields || [];
  355. // Remove fields set by previous radio change
  356. field.options.forEach(option => {
  357. option.fields.forEach(f => {
  358. requiredFields = requiredFields.filter(rf => rf !== f.name)
  359. })
  360. })
  361. field.options.forEach(option => {
  362. if (option.value === currentValue) {
  363. option.fields.forEach(optionField => {
  364. if (optionField.required) {
  365. requiredFields.push(optionField.name);
  366. }
  367. })
  368. }
  369. })
  370. this.setState({ requiredFields: requiredFields });
  371. }
  372. areFieldsDisabled() {
  373. return (this.state.endpointStatus === endpointStatuses.VALIDATING
  374. || this.state.endpointStatus === endpointStatuses.SUCCESS)
  375. }
  376. /**
  377. * Handler to change the endpoint field
  378. * @param e
  379. * @param field
  380. */
  381. handleCloudFieldChange(e, field) {
  382. let currentCloudData = this.state.currentCloudData
  383. if (field.type == 'dropdown') {
  384. currentCloudData[field.name] = e.value
  385. } else if (field.type === 'switch') {
  386. currentCloudData[field.name] = e.target.checked
  387. } else {
  388. currentCloudData[field.name] = e.target.value
  389. }
  390. if (field.type === 'switch-radio') {
  391. this.setRadioRequiredFields(field, e.target.value)
  392. }
  393. this.setState({ currentCloudData: currentCloudData })
  394. }
  395. handleCopyErrorClick() {
  396. let succesful = Helper.copyTextToClipboard(this.state.errorMessage)
  397. if (succesful) {
  398. NotificationActions.notify('The error message has been copied to clipboard.')
  399. } else {
  400. NotificationActions.notify('The error message couldn\'t be copied', 'error')
  401. }
  402. }
  403. handleShowErrorClick() {
  404. this.setState({
  405. showErrorMessage: !this.state.showErrorMessage
  406. })
  407. }
  408. /**
  409. * Renders the cloud list
  410. * @returns {XML}
  411. */
  412. renderCloudList() {
  413. let clouds = this.state.allClouds.map((cloud, index) => {
  414. let colorType = ""
  415. if (cloud.credentials != null && cloud.credentials.length != 0) {
  416. colorType = ""
  417. }
  418. return (
  419. <div className={s.cloudContainer} key={"cloudImage_" + index}>
  420. <div
  421. className={s.cloudImage + " icon large-cloud " + cloud.name + " " + colorType}
  422. onClick={() => this.chooseCloud(cloud)}
  423. ></div>
  424. </div>
  425. )
  426. }, this)
  427. return (
  428. <div className={s.container}>
  429. <div className={s.cloudList}>
  430. {clouds}
  431. </div>
  432. <div className={s.buttons}>
  433. <button className={s.centerBtn + " gray"} onClick={(e) => this.handleCancel(e)}>Cancel</button>
  434. </div>
  435. </div>
  436. )
  437. }
  438. /**
  439. * Renders individual cloud fields
  440. * @param field
  441. * @returns {XML}
  442. */
  443. renderField(field) {
  444. let returnValue
  445. switch (field.type) {
  446. case "text":
  447. returnValue = (
  448. <div className={"form-group " + (this.isValid(field) ? "" : s.error) + (field.required ? ' required' : '')}
  449. key={"cloudField_" + field.name}
  450. >
  451. <div className="input-label">{field.label}</div>
  452. <input
  453. type="text"
  454. placeholder={field.label}
  455. disabled={this.areFieldsDisabled()}
  456. onChange={(e) => this.handleCloudFieldChange(e, field)}
  457. value={this.state.currentCloudData[field.name] || ''}
  458. />
  459. </div>
  460. )
  461. break;
  462. case "password":
  463. returnValue = (
  464. <div className={"form-group " + (this.isValid(field) ? "" : s.error) + (field.required ? ' required' : '')}
  465. key={"cloudField_" + field.name}
  466. >
  467. <div className="input-label">{field.label}</div>
  468. <input
  469. type="password"
  470. placeholder={field.label}
  471. disabled={this.areFieldsDisabled()}
  472. onChange={(e) => this.handleCloudFieldChange(e, field)}
  473. value={this.state.currentCloudData[field.name]}
  474. />
  475. </div>
  476. )
  477. break;
  478. case 'switch':
  479. returnValue = (
  480. <div
  481. className="form-group"
  482. key={"cloudField_" + field.name}
  483. >
  484. <div className="input-label">
  485. {field.label + (field.required ? " *" : "")}
  486. </div>
  487. <Switch
  488. className={s.switchButton}
  489. labelClassName={s.switchLabel}
  490. checked={this.state.currentCloudData[field.name] === true}
  491. onChange={(e) => this.handleCloudFieldChange(e, field)}
  492. checkedLabel="Yes"
  493. uncheckedLabel="No"
  494. />
  495. </div>
  496. )
  497. break
  498. case "dropdown":
  499. returnValue = (
  500. <div className={"form-group " + (this.isValid(field) ? "" : s.error)} key={"cloudField_" + field.name}>
  501. <div className="input-label">
  502. {field.label + (field.required ? " *" : "")}
  503. </div>
  504. <Dropdown
  505. disabled={this.areFieldsDisabled()}
  506. options={field.options}
  507. onChange={(e) => this.handleCloudFieldChange(e, field)}
  508. placeholder="Choose a value"
  509. value={field.options.find(function findOption(option) { return option.value == this},
  510. this.state.currentCloudData[field.name])}
  511. />
  512. </div>
  513. )
  514. break;
  515. case "switch-radio":
  516. let fields = ""
  517. field.options.forEach((option) => {
  518. if (option.value == this.state.currentCloudData[field.name]) {
  519. fields = option.fields.map((optionField) => this.renderField(optionField))
  520. }
  521. })
  522. let radioOptions = field.options.map((option, key) => (
  523. <div key={"radio_option_" + key} className={s.radioOption}>
  524. <input
  525. disabled={this.areFieldsDisabled()}
  526. type="radio"
  527. value={option.value}
  528. id={option.name}
  529. checked={option.value == this.state.currentCloudData[field.name]}
  530. onChange={(e) => this.handleCloudFieldChange(e, field)}
  531. /> <label htmlFor={option.name}>{option.label}</label>
  532. </div>
  533. )
  534. )
  535. returnValue = (
  536. <div key={"cloudField_" + field.name}>
  537. <div className="form-group switch-radio" key={"cloudField_" + field.name}>
  538. { radioOptions }
  539. </div>
  540. <div></div>
  541. <div className={s.cloudFields + ' ' + s.radioFields}>
  542. {fields}
  543. </div>
  544. </div>
  545. )
  546. break;
  547. default:
  548. break
  549. }
  550. return returnValue
  551. }
  552. renderEndpointErrorMessage() {
  553. if (this.state.endpointStatus !== endpointStatuses.ERROR || !this.state.showErrorMessage) {
  554. return null
  555. }
  556. return (
  557. <div className={s.endpointErrorMessage}
  558. onClick={() => this.handleCopyErrorClick()}
  559. onMouseDown={e => e.stopPropagation()}
  560. onMouseUp={e => e.stopPropagation()}
  561. >
  562. <span className={s.endpointErrorMessageContent}>
  563. {this.state.errorMessage}<span className="copyButton" />
  564. </span>
  565. </div>
  566. )
  567. }
  568. renderEndpointErrorTitle() {
  569. let errorMessage = null
  570. if (this.state.errorMessage) {
  571. errorMessage = (
  572. <span className={s.ednpointErrorMessageViewMore}
  573. onClick={() => { this.handleShowErrorClick() }}
  574. >{this.state.showErrorMessage ? 'Hide Error' : 'Show Error'}</span>
  575. )
  576. }
  577. return (
  578. <div className={s.endpointErrorMessageTitle}>
  579. <span className={s.endpointErrorMessageTitleContent}>Validation Failed</span>
  580. {errorMessage}
  581. </div>
  582. )
  583. }
  584. renderEndpointStatus() {
  585. if (this.state.endpointStatus === endpointStatuses.SUCCESS) {
  586. clearTimeout(this.closeTimeout)
  587. this.closeTimeout = setTimeout(() => {
  588. this.closeTimeout = null
  589. this.handleClose()
  590. }, 2000)
  591. }
  592. let endpointStatus = null
  593. if (this.state.endpointStatus === endpointStatuses.ERROR ||
  594. this.state.endpointStatus === endpointStatuses.SUCCESS) {
  595. let icon = 'successIcon'
  596. let content = 'Endpoint is Valid'
  597. if (this.state.endpointStatus === endpointStatuses.ERROR) {
  598. icon = 'errorIcon'
  599. content = this.renderEndpointErrorTitle()
  600. }
  601. endpointStatus = (
  602. <div className={s.endpointStatus}>
  603. <div className={s.endpointStatusTitle}>
  604. <div className={s.endpointStatusIcon + ' ' + icon}></div>
  605. <div className={s.endpointStatusLabel}>
  606. {content}
  607. </div>
  608. </div>
  609. {this.renderEndpointErrorMessage()}
  610. </div>
  611. )
  612. }
  613. return endpointStatus
  614. }
  615. renderButtons() {
  616. let cancelButton = (this.state.type == "new" && this.props.cloud == null) ?
  617. <button className={s.leftBtn + " gray"} onClick={(e) => this.handleBack(e)}>Back</button> :
  618. <button className={s.leftBtn + " gray"} onClick={(e) => this.handleCancel(e)}>Cancel</button>
  619. let saveButtonContent = 'Validate and Save'
  620. if (this.state.endpointStatus === endpointStatuses.VALIDATING ||
  621. this.state.endpointStatus === endpointStatuses.SUCCESS) {
  622. let text = this.state.endpointStatus === endpointStatuses.VALIDATING ? 'Validating' : 'Saving'
  623. saveButtonContent = <span>{text} ... <div className="spinner"></div></span>
  624. }
  625. let saveButton = (
  626. <button
  627. className={s.rightBtn}
  628. onClick={this.handleSave.bind(this)}
  629. disabled={this.areFieldsDisabled()}
  630. >
  631. {saveButtonContent}
  632. </button>
  633. )
  634. return (
  635. <div className={s.buttons}>
  636. {cancelButton}
  637. {saveButton}
  638. </div>
  639. )
  640. }
  641. /**
  642. * Renders the new/edit endpoint form
  643. * @param cloud
  644. * @returns {XML}
  645. */
  646. renderCloudFields(cloud) {
  647. let fields = cloud.endpoint.fields.map(field => this.renderField(field), this)
  648. return (
  649. <div className={s.container}>
  650. <div className={s.cloudImage}>
  651. <div className={" icon large-cloud " + this.state.currentCloud.name}></div>
  652. </div>
  653. {this.renderEndpointStatus()}
  654. <div className={s.cloudFields + (cloud.endpoint.fields.length > 6 ? " " + s.larger : "")}>
  655. <div className={"form-group " + (this.state.cloudFormsSubmitted &&
  656. this.state.connectionName.trim().length == 0 ? s.error : "") + ' required'}
  657. >
  658. <div className="input-label">
  659. Endpoint Name
  660. </div>
  661. <input
  662. type="text"
  663. placeholder="Endpoint Name"
  664. disabled={this.areFieldsDisabled()}
  665. onChange={(e) => this.handleChangeName(e)}
  666. value={this.state.connectionName}
  667. />
  668. </div>
  669. <div className="form-group">
  670. <div className="input-label">
  671. Endpoint Description
  672. </div>
  673. <input
  674. type="text"
  675. disabled={this.areFieldsDisabled()}
  676. placeholder="Endpoint Description"
  677. onChange={(e) => this.handleChangeDescription(e)}
  678. value={this.state.description}
  679. ></input>
  680. </div>
  681. {fields}
  682. </div>
  683. {this.renderButtons()}
  684. </div>
  685. )
  686. }
  687. render() {
  688. let modalBody
  689. if (this.state.currentCloud == null) {
  690. if (this.state.allClouds) {
  691. modalBody = this.renderCloudList()
  692. } else {
  693. modalBody = <LoadingIcon />
  694. }
  695. } else {
  696. modalBody = this.renderCloudFields(this.state.currentCloud)
  697. }
  698. return (
  699. <div tabIndex="0" className={s.root} ref={rootDiv => { this.rootDiv = rootDiv }}>
  700. <div className={s.header}>
  701. <h3>{this.props.type === 'edit' ? 'Edit Cloud Connection' : title}</h3>
  702. </div>
  703. {modalBody}
  704. </div>
  705. );
  706. }
  707. }
  708. export default withStyles(AddCloudConnection, s);