3 * Copyright 2016 RIFT.IO Inc
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
19 import React from 'react';
20 import Button from 'widgets/button/rw.button.js';
21 import _cloneDeep from 'lodash/cloneDeep';
22 import _isEmpty from 'lodash/isEmpty';
23 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
24 import Crouton from 'react-crouton';
25 import TextInput from 'widgets/form_controls/textInput.jsx';
26 import {AccountConnectivityStatus} from '../account_sidebar/accountSidebar.jsx';
27 import Utils from 'utils/utils.js';
28 import 'style/common.scss';
29 import './account.scss';
31 function isAccessDenied (error) {
32 const rpcResult = Utils.rpcError(error);
33 return rpcResult && rpcResult['rpc-reply']['rpc-error']['error-tag'] === 'access-denied';
36 class Account extends React.Component {
40 // console.log(this.state.account)
42 storeListener = (state) => {
44 if((!state.account || _isEmpty(state.account)) && state.userProfile) {
45 this.setUp(this.props, state.savedData)
47 state.account && state.account.params && this.setState({
48 account: state.account,
49 accountType: state.accountType,
51 sdnOptions: state.sdnOptions,
52 savedData: state.savedData,
53 userProfile: state.userProfile
57 componentWillMount() {
58 this.state = this.props.store.getState();
59 this.props.store.listen(this.storeListener);
61 componentWillUpdate(nextProps, nextState, nextContext) {
62 if(!_isEmpty(nextContext.userProfile) && !nextState.userProfile) {
63 this.props.store.getTransientAccountForUser(nextContext.userProfile)
66 componentWillReceiveProps(nextProps) {
67 if(JSON.stringify(nextProps.params) != JSON.stringify(this.props.params)){
68 this.setUp(nextProps);
71 componentWillUnmount() {
72 console.log('unmounting')
73 // this.setState({account: null, accountType: null, types: []})
74 this.props.store.unlisten(this.storeListener);
76 setUp(props, savedData){
77 console.log('Setting up');
78 var SD = savedData || this.state.savedData;
79 if(props.params.name && props.params.name != 'create') {
80 if (SD && SD == props.params.name) {
81 this.props.store.viewAccount({type: props.params.type, name: props.params.name}, SD);
83 this.props.store.viewAccount({type: props.params.type, name: props.params.name});
87 this.props.store.setAccountTemplate(props.params.type, null, SD);
93 var Account = this.state.account;
94 let AccountType = this.state.accountType;
95 if (Account.name == "") {
96 self.props.flux.actions.global.showNotification("Please give the account a name");
99 if(!wasAllDetailsFilled(Account)) {
100 self.props.flux.actions.global.showNotification("Please fill all account details");
105 let newAccount = _cloneDeep(removeTrailingWhitespace(Account));
107 delete newAccount.params;
109 newAccount.nestedParams &&
110 newAccount.nestedParams['container-name'] &&
111 delete newAccount[newAccount.nestedParams['container-name']];
113 delete newAccount.nestedParams;
115 if(AccountType == 'resource-orchestrator') {
116 newAccount['ro-account-type'] = newAccount['account-type'] || newAccount['ro-account-type'];
117 delete newAccount['account-type'];
119 if(AccountType == 'cloud' && self.props.vduInstanceTimeout != '') {
120 newAccount['vdu-instance-timeout'] = self.props.vduInstanceTimeout;
122 this.props.flux.actions.global.showScreenLoader();
123 this.props.store.create(newAccount, AccountType).then(
125 self.props.router.push({pathname:'accounts'});
126 self.props.flux.actions.global.hideScreenLoader.defer();
129 self.props.flux.actions.global.showNotification(error);
130 self.props.flux.actions.global.hideScreenLoader.defer();
137 var Account = this.state.account;
138 let AccountType = this.state.accountType;
140 if(!wasAllDetailsFilled(Account)) {
141 self.props.flux.actions.global.showNotification("Please fill all account details");
145 if(AccountType == 'cloud' && self.props.vduInstanceTimeout != '') {
146 Account['vdu-instance-timeout'] = self.props.vduInstanceTimeout;
148 this.props.flux.actions.global.showScreenLoader();
149 this.props.store.update(Account, AccountType).then(
151 self.props.router.push({pathname:'accounts'});
152 self.props.flux.actions.global.hideScreenLoader();
155 let msg = isAccessDenied(error) ?
156 "Update of account failed. No authorization to modify accounts."
157 : "Update of account failed. This could be because the account is in use or no longer exists.";
158 self.props.flux.actions.global.hideScreenLoader.defer();
159 self.props.flux.actions.global.showNotification.defer(msg);
166 this.props.flux.actions.global.handleCancelAccount();
167 this.props.router.push({pathname:'accounts'});
169 handleDelete = () => {
171 let msg = 'Preparing to delete "' + self.state.account.name + '"' +
172 ' Are you sure you want to delete this ' + self.state.accountType + ' account?"';
173 if (window.confirm(msg)) {
174 this.props.flux.actions.global.showScreenLoader();
175 this.props.store.delete(self.state.accountType, self.state.account.name).then(
177 self.props.flux.actions.global.hideScreenLoader();
178 self.props.router.push({pathname:'accounts'});
181 let msg = isAccessDenied(error) ?
182 "Deletion of account failed. No authorization to delete accounts."
183 : "Deletion of account failed. This could be because the account is in use or has already been deleted.";
184 self.props.flux.actions.global.hideScreenLoader.defer();
185 self.props.flux.actions.global.showNotification.defer(msg);
190 handleNameChange(event) {
191 this.props.store.handleNameChange(event);
193 handleAccountTypeChange(node, isRo, event) {
194 this.props.store.handleAccountTypeChange(node, isRo, event);
196 handleSelectSdnAccount = (e) => {
197 var tmp = this.state.account;
199 tmp['sdn-account'] = e;
201 if(tmp['sdn-account']) {
202 delete tmp['sdn-account'];
207 updateVduInstanceTimeout(event) {
208 this.props.store.updateVduTimeout(event)
210 preventDefault = (e) => {
214 evaluateSubmit = (e) => {
215 if (e.keyCode == 13) {
216 if (this.props.params.name != 'create') {
228 let {store, ...props} = this.props;
229 // This section builds elements that only show up on the create page.
230 // var name = <label>Name <input type="text" onChange={this.handleNameChange.bind(this)} style={{'textAlign':'left'}} /></label>;
231 let Account = this.state.account || {};
232 var name = <TextInput label="Name" onChange={this.handleNameChange.bind(this)} required={true} value={Account &&Account.name || ''} />;
234 let selectAccount = null;
235 let resfreshStatus = null;
236 // AccountType is for the view, not the data account-type value;
237 let AccountType = this.state.accountType;
238 let Types = this.state.types;
239 let isEdit = this.props.params.name != 'create';
241 let cloudResources = Account['cloud-resources-state'] && Account['cloud-resources-state'][Account['account-type']];
242 let cloudResourcesStateHTML = null;
243 // Account Type Radio
244 var selectAccountStack = [];
245 let setVduTimeout = null;
248 <Button key="0" onClick={this.cancel} className="cancel light" label="Cancel"></Button>,
249 <Button key="1" role="button" onClick={this.create.bind(this)} className="save dark" label="Save" />
251 for (var i = 0; i < Types.length; i++) {
253 const isRo = node.hasOwnProperty('ro-account-type');
254 var isSelected = (Account['account-type'] || Account['ro-account-type']) == (node['account-type'] || node['ro-account-type']);
255 selectAccountStack.push(
256 <label key={i} className={"accountSelection " + (isSelected ? "accountSelection--isSelected" : "")}>
257 <div className={"accountSelection-overlay" + (isSelected ? "accountSelection-overlay--isSelected" : "")}></div>
258 <div className="accountSelection-imageWrapper">
259 <img src={store.getImage(node['account-type'] || node['ro-account-type'])}/>
261 <input type="radio" name="account" onChange={this.handleAccountTypeChange.bind(this, node, isRo)} defaultChecked={node.name == Types[0].name} value={node['account-type'] || node['ro-account-type']} />{node.name}
266 <div className="accountForm">
267 <h3 className="accountForm-title">Select Account Type</h3>
268 <div className="select-type accountForm-content" >
277 //Cloud Account: SDN Account Option
278 let sdnAccounts = null;
279 if( (AccountType == 'cloud') ) {
280 if ( !isEdit && (this.state.sdnOptions.length > 1 )) {
282 <div className="associateSdnAccount accountForm">
283 <h3 className="accountForm-title">Associate SDN Account</h3>
284 <div className="accountForm-content">
285 <SelectOption options={this.state.sdnOptions} onChange={this.handleSelectSdnAccount} />
291 if(isEdit && Account['sdn-account']) {
292 sdnAccounts = ( <div className="associateSdnAccount">SDN Account: {Account['sdn-account']} </div>)
295 //END Cloud Account: SDN Account Option
297 setVduTimeout = <TextInput
298 className="accountForm-input"
299 label="VIM Instantiation Timeout (Seconds)"
300 onChange={this.updateVduInstanceTimeout.bind(this)}
301 value={this.props.vduInstanceTimeout}
302 readonly={self.props.readonly}
307 // This sections builds the parameters for the account details.
308 if (Account.params) {
309 var paramsStack = [];
310 var optionalField = '';
311 var isRo = Account.hasOwnProperty('ro-account-type');
312 for (var i = 0; i < Account.params.length; i++) {
313 var node = Account.params[i];
316 if (Account[Account['ro-account-type']] && Account[Account['ro-account-type']][node.ref]) {
317 value = Account[Account['ro-account-type']][node.ref]
320 if (Account[Account['account-type']] && Account[Account['account-type']][node.ref]) {
321 value = Account[Account['account-type']][node.ref]
324 if (this.props.edit && Account.params) {
325 value = Account.params[node.ref];
327 if(node.ref == "password" || node.ref == "secret") {
328 let displayValue = value;
329 if (self.props.readonly) {
330 displayValue = new Array(value.length + 1).join( '*' );
335 className="accountForm-input"
337 required={!node.optional}
338 onChange={this.props.store.handleParamChange(node, isRo)}
340 readonly={self.props.readonly}
348 className="accountForm-input"
350 required={!node.optional}
351 onChange={this.props.store.handleParamChange(node, isRo)}
353 readonly={self.props.readonly}
359 let nestedParamsStack = null;
360 if (Account.nestedParams) {
361 nestedParamsStack = [];
362 var optionalField = '';
363 for (var i = 0; i < Account.nestedParams.params.length; i++) {
364 var node = Account.nestedParams.params[i];
366 if (Account[Account['account-type']] && Account.nestedParams && Account[Account['account-type']][Account.nestedParams['container-name']] && Account[Account['account-type']][Account.nestedParams['container-name']][node.ref]) {
367 value = Account[Account['account-type']][Account.nestedParams['container-name']][node.ref];
370 optionalField = <span className="optional">Optional</span>;
372 // nestedParamsStack.push(
373 // <label key={node.label}>
374 // <label className="create-fleet-pool-params">{node.label} {optionalField}</label>
375 // <input className="create-fleet-pool-input" type="text" onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)} value={value}/>
378 if(node.ref == "password" || node.ref == "secret") {
379 let displayValue = value;
380 if (self.props.readonly) {
381 displayValue = new Array(value.length + 1).join( '*' );
383 nestedParamsStack.push(
387 required={!node.optional}
388 className="create-fleet-pool-input"
390 onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)}
392 readonly={self.props.readonly}
396 nestedParamsStack.push(
400 required={!node.optional}
401 className="create-fleet-pool-input"
403 onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)}
405 readonly={self.props.readonly}
413 <li className="create-fleet-pool accountForm">
414 <h3 className="accountForm-title"> {isEdit ? 'Update' : 'Enter'} Account Details</h3>
415 <div className="accountForm-content">
417 <div className="accountForm-nestedParams">
425 <li className="create-fleet-pool accountForm">
426 <h3 className="accountForm-title"> {isEdit ? 'Update' : 'Enter'}</h3>
427 <label style={{'marginLeft':'17px', color:'#888'}}>No Details Required</label>
432 // This section builds elements that only show up in the edit page.
434 name = <label>{Account.name}</label>;
436 <Button key="2" onClick={this.handleDelete} className="light" label="Remove Account" />,
437 <Button key="3" onClick={this.cancel} className="light" label="Cancel" />,
438 <Button key="4" role="button" onClick={this.update.bind(this)} className="update dark" label="Update" />
440 resfreshStatus = Account['connection-status'] ? (
441 <div className="accountForm">
442 <div className="accountForm-title accountForm-title--edit">
445 <div className="accountForm-content" style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
446 <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
447 <AccountConnectivityStatus status={Account['connection-status'].status} />
448 {Account['connection-status'] && Account['connection-status'].status && Account['connection-status'].status.toUpperCase()}
450 { self.props.readonly ? null :
451 <Button is-disabled={self.props.readonly} className="refreshList light" onClick={this.props.store.refreshAccount.bind(this, Account.name, AccountType)} label="REFRESH STATUS" />
455 (Account['connection-status'] && Account['connection-status'].status && Account['connection-status'].status.toUpperCase()) === 'FAILURE' ?
456 displayFailureMessage(Account['connection-status'].details) : null
465 <form className="app-body create Accounts" onSubmit={this.preventDefault} onKeyDown={this.evaluateSubmit}>
466 <div className="noticeSubText noticeSubText_right">
469 <div className="associateSdnAccount accountForm">
470 <h3 className="accountForm-title">Account</h3>
471 <div className="accountForm-content" style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
475 <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
476 <img src={store.getImage(Account['account-type'] || Account['ro-account-type'])}/> {props.AccountMeta.labelByType[Account['account-type'] || Account['ro-account-type']]}
482 AccountType == 'cloud' ? (
483 <div className="accountForm-content">
494 {cloudResourcesStateHTML}
495 <ol className="flex-row">
498 <div className="form-actions">
499 {!self.props.readonly ? buttons : null}
503 return Types.length ? html : null;
507 Account.contextTypes = {
508 router: React.PropTypes.object,
509 userProfile: React.PropTypes.object
512 function displayFailureMessage(msg) {
514 <div className="accountForm-content" style={{maxWidth: '600px'}}>
515 <div style={{paddingBottom: '1rem'}}>Details:</div>
524 class SelectOption extends React.Component {
528 handleOnChange = (e) => {
529 this.props.onChange(JSON.parse(e.target.value));
534 <select className={this.props.className} onChange={this.handleOnChange}>
536 this.props.options.map(function(op, i) {
537 return <option key={i} value={JSON.stringify(op.value)}>{op.label}</option>
545 SelectOption.defaultProps = {
547 onChange: function(e) {
552 function wasAllDetailsFilled(Account) {
553 var type = Account['account-type'] || Account['ro-account-type'];
554 var params = Account.params;
557 for (var i = 0; i < params.length; i++) {
558 var param = params[i].ref;
559 if (typeof(Account[type]) == 'undefined' || typeof(Account[type][param]) == 'undefined' || Account[type][param] == "") {
560 if (!params[i].optional) {
567 let nestedParams = Account.nestedParams && Account.nestedParams;
568 if (nestedParams && nestedParams.params) {
569 for (let i = 0; i < nestedParams.params.length; i++) {
570 let nestedParam = nestedParams.params[i].ref;
571 if (typeof(Account[type]) == 'undefined' || typeof(Account[type][nestedParams['container-name']][nestedParam]) == 'undefined' || Account[type][nestedParams['container-name']][nestedParam] == "") {
572 if (!nestedParams.params[i].optional) {
581 function removeTrailingWhitespace(Account) {
582 var type = Account['account-type'] || Account['ro-account-type'];
583 var params = Account.params;
586 for (var i = 0; i < params.length; i++) {
587 var param = params[i].ref;
588 if(typeof(Account[type][param]) == 'string') {
589 Account[type][param] = Account[type][param].trim();
594 let nestedParams = Account.nestedParams;
595 if (nestedParams && nestedParams.params) {
596 for (let i = 0; i < nestedParams.params.length; i++) {
597 let nestedParam = nestedParams.params[i].ref;
598 let nestedParamValue = Account[type][nestedParams['container-name']][nestedParam];
599 if (typeof(nestedParamValue) == 'string') {
600 Account[type][nestedParams['container-name']][nestedParam] = nestedParamValue.trim();
607 export default SkyquakeComponent(Account)