/* Copyright (C) 2017 Cloudbase Solutions SRL This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import React, { PropTypes } from 'react'; import withStyles from 'isomorphic-style-loader/lib/withStyles'; import s from './AddCloudConnection.scss'; import Reflux from 'reflux'; import ConnectionsStore from '../../stores/ConnectionsStore'; import ConnectionsActions from '../../actions/ConnectionsActions'; import NotificationActions from '../../actions/NotificationActions'; import Dropdown from '../NewDropdown'; import LoadingIcon from "../LoadingIcon/LoadingIcon"; import ValidateEndpoint from '../ValidateEndpoint'; const title = 'Add Cloud Endpoint'; class AddCloudConnection extends Reflux.Component { static contextTypes = { onSetTitle: PropTypes.func.isRequired, }; static defaultProps = { cloud: null, connection: null, type: "new" } constructor(props) { super(props) this.store = ConnectionsStore this.state = { type: props.type, // type of operation: new/edit connection: props.connection, // connection object (on edit) connectionName: "", // connection name field description: "", // connection description field currentCloud: this.props.cloud, // chosen cloud - if adding a new endpoint currentCloudData: null, // endpoint field data validateEndpoint: false, // holds the endpoint object when validation requiredFields: [], // array that holds all the endpoint required fields - used for field validation cloudFormsSubmitted: false // flag that indicates if the form has been submitted - used for field validation } } componentWillMount() { super.componentWillMount.call(this) this.context.onSetTitle(title); } componentDidMount() { if (this.state.connection) { this.state.allClouds.forEach(item => { if (item.name === this.state.connection.type) { let credentials = this.state.connection.credentials let newCredentials = {} for (let i in credentials) { if (typeof credentials[i] == "object") { newCredentials['login_type'] = i // credentials['user_credentials'] = {} for (let j in credentials[i]) { // credentials['user_credentials'][j] = credentials[i][j] newCredentials[j] = credentials[i][j] } } else { newCredentials[i] = credentials[i] + "" } } this.setState({ currentCloudData: newCredentials, connectionName: this.state.connection.name, description: this.state.connection.description }, () => { this.chooseCloud(item) }) } }) } else if (this.props.cloud) { this.chooseCloud(this.props.cloud) } } /** * Function called upon saving an endpoint - handles both new and edit operations */ handleSave() { let valid = true let requiredFields = this.state.requiredFields // If Azure, validate fields manually if (this.state.currentCloud.name == "azure") { if (this.state.currentCloudData.login_type == "user_credentials") { requiredFields = ["subscription_id", "username", "password"] } else if (this.state.currentCloudData.login_type == "service_principal_credentials") { requiredFields = ["subscription_id", "tenant_id", "client_id", "client_secret"] } else { valid = false NotificationActions.notify("Please choose the login type", "error") } this.setState({ requiredFields: requiredFields }) } for (let i in this.state.currentCloudData) { if (requiredFields.indexOf(i) > -1 && !this.state.currentCloudData[i]) { valid = false } } requiredFields.forEach((field) => { if (!this.state.currentCloudData[field]) { valid = false } }) if (this.state.connectionName.trim().length == 0) { valid = false } if (!valid) { NotificationActions.notify("Please fill all required fields", "error") this.setState({ cloudFormsSubmitted: true }) } else { let credentials = Object.assign({}, this.state.currentCloudData) for (let key in credentials) { if (credentials[key].label) { credentials[key] = credentials[key].value } let field = this.state.currentCloud.endpoint.fields.find(function findByName(f) { return f.name == this }, key); // Convert datatype switch (field.dataType) { case 'boolean': credentials[key] = (credentials[key] === true || ((typeof credentials[key] === 'string' || credentials[key] instanceof String) && credentials[key].toLowerCase() == "true")); break; case 'integer': let value = parseInt(credentials[key], 10); if (value.toString() != credentials[key]) { valid = false; NotificationActions.notify('"' + key + '" needs to be an integer', 'error'); } credentials[key] = value; break; default: // retain original value break; } } if (!valid) { return; } // If Azure, explicitly setting the fields right if (this.state.currentCloud.name == "azure") { credentials = {} credentials["subscription_id"] = this.state.currentCloudData["subscription_id"] if (this.state.currentCloudData["login_type"] == "user_credentials") { credentials["user_credentials"] = { username: this.state.currentCloudData["username"], password: this.state.currentCloudData["password"] } } else { credentials["service_principal_credentials"] = { tenant_id: this.state.currentCloudData["tenant_id"], client_id: this.state.currentCloudData["client_id"], client_secret: this.state.currentCloudData["client_secret"] } } } // Remove the login_type since it is not needed if (this.state.currentCloud.name == "azure") { delete credentials.login_type } // If endpoint is new if (this.state.type == "new") { ConnectionsActions.newEndpoint({ name: this.state.connectionName, description: this.state.description, type: this.state.currentCloud.name, connection_info: credentials }, (response) => { this.setState({ validateEndpoint: response.data.endpoint, type: "edit", connection: response.data.endpoint }) }) this.props.addHandle(this.state.connectionName); } else { // If editing an endpoint ConnectionsActions.editEndpoint(this.state.connection, { name: this.state.connectionName, description: this.state.description, connection_info: credentials }, (response) => { this.setState({ validateEndpoint: response.data.endpoint, type: "edit", connection: response.data.endpoint }) this.props.updateHandle({ name: this.state.connectionName, description: this.state.description }) }) } } } /** * Handles change `name` property * @param e */ handleChangeName(e) { this.setState({ connectionName: e.target.value }) } /** * Handles change `description` property * @param e */ handleChangeDescription(e) { this.setState({ description: e.target.value }) } /** * Handler to choose the cloud which the endpoint will be assigned to * @param cloud */ chooseCloud(cloud) { let currentCloudData = {} if (this.state.currentCloudData !== null) { currentCloudData = this.state.currentCloudData } let requiredFields = [] cloud.endpoint.fields.forEach(field => { if (typeof currentCloudData[field.name] == "undefined") { if (field.type == "dropdown") { let defaultOption = field.options.find(function isDefaultOption(option) { return option.default; }) if (defaultOption) { currentCloudData[field.name] = defaultOption.value; } else { currentCloudData[field.name] = null; } } else { currentCloudData[field.name] = "" } } if (field.required) { requiredFields.push(field.name) } }) this.setState({ currentCloud: cloud, currentCloudData: currentCloudData, requiredFields: requiredFields }, this.setDefaultValues) } /** * Function that goes back from endpoint validation to edit mode */ backToEdit() { this.setState({ validateEndpoint: null }) } /** * Handles back operation when adding a new endpoint and want to switch cloud. Resets all previous cloud data. */ handleBack() { this.setState({ currentCloudData: null, currentCloud: null, requiredFields: null, connectionName: "", description: null }) } /** * Sets default values for cloud fields */ setDefaultValues() { this.state.currentCloud.endpoint.fields.forEach(field => { let currentCloudData = this.state.currentCloudData switch (field.type) { case 'dropdown': field.options.forEach(option => { if (option.default === true && typeof currentCloudData[field.name] == "undefined") { currentCloudData[field.name] = option.value this.setState({ currentCloudData: currentCloudData }) } }, this) break case 'switch-radio': field.options.forEach(option => { if (option.default && typeof currentCloudData[field.name] == "undefined") { currentCloudData[field.name] = option.value this.setState({ currentCloudData: currentCloudData }) } }, this) break; case 'text': if (field.default && typeof currentCloudData[field.name] == "undefined") { currentCloudData[field.name] = field.default this.setState({ currentCloudData: currentCloudData }) } break default: break; } }, this) } /** * Checks wether the field is valid. Only goes through validation if field is required * @param field * @returns {boolean} */ isValid(field) { if (field.required && this.state.cloudFormsSubmitted) { if (this.state.currentCloudData[field.name]) { if (this.state.currentCloudData[field.name] && this.state.currentCloudData[field.name].length == 0) { return false } else { return true } } else { return false } } else { return true } } /** * Handles cancel edit/add endpoint */ handleCancel() { this.props.closeHandle(); } /** * Handler to change the endpoint field * @param e * @param field */ handleCloudFieldChange(e, field) { let currentCloudData = this.state.currentCloudData if (field.type == 'dropdown') { currentCloudData[field.name] = e.value } else { currentCloudData[field.name] = e.target.value } this.setState({ currentCloudData: currentCloudData }) } /** * Renders the cloud list * @returns {XML} */ renderCloudList() { let clouds = this.state.allClouds.map((cloud, index) => { let colorType = "" if (cloud.credentials != null && cloud.credentials.length != 0) { colorType = "" } return (
this.chooseCloud(cloud)} >
) }, this) return (
{clouds}
) } /** * Renders individual cloud fields * @param field * @returns {XML} */ renderField(field) { let returnValue switch (field.type) { case "text": returnValue = (
this.handleCloudFieldChange(e, field)} value={this.state.currentCloudData[field.name]} />
) break; case "password": returnValue = (
this.handleCloudFieldChange(e, field)} value={this.state.currentCloudData[field.name]} />
) break; case "dropdown": returnValue = (
this.handleCloudFieldChange(e, field)} placeholder="Choose a value" value={field.options.find(function findOption(option) { return option.value == this}, this.state.currentCloudData[field.name])} />
) break; case "switch-radio": let fields = "" field.options.forEach((option) => { if (option.value == this.state.currentCloudData[field.name]) { fields = option.fields.map((optionField) => this.renderField(optionField)) } }) let radioOptions = field.options.map((option, key) => (
this.handleCloudFieldChange(e, field)} />
) ) returnValue = (
{ radioOptions }
{fields}
) break; default: break } return returnValue } /** * Renders the new/edit endpoint form * @param cloud * @returns {XML} */ renderCloudFields(cloud) { if (this.state.currentCloudData == null) { this.setState({ currentCloudData: {} }) } let fields = cloud.endpoint.fields.map(field => this.renderField(field), this) return (
6 ? " " + s.larger : "")}>
this.handleChangeName(e)} value={this.state.connectionName} />
this.handleChangeDescription(e)} value={this.state.description} >
{fields}
{(this.state.type == "new" && this.props.cloud == null) ? ( ) : ( )}
) } render() { let modalBody if (this.state.validateEndpoint) { modalBody = ( this.backToEdit(e)} /> ) } else if (this.state.currentCloud == null) { if (this.state.allClouds) { modalBody = this.renderCloudList() } else { modalBody = } } else { modalBody = this.renderCloudFields(this.state.currentCloud) } return (

{title}

{modalBody}
); } } export default withStyles(AddCloudConnection, s);