update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / accounts / src / account / account.jsx
1 /*
2  *
3  *   Copyright 2016 RIFT.IO Inc
4  *
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
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  */
18
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';
30
31 function isAccessDenied (error) {
32     const rpcResult = Utils.rpcError(error);
33     return rpcResult && rpcResult['rpc-reply']['rpc-error']['error-tag'] === 'access-denied';
34 }
35
36 class Account extends React.Component {
37     constructor(props) {
38         super(props);
39
40         // console.log(this.state.account)
41     }
42     storeListener = (state) => {
43         if(state.socket) {
44             if((!state.account || _isEmpty(state.account)) && state.userProfile) {
45                 this.setUp(this.props, state.savedData)
46             }
47             state.account && state.account.params && this.setState({
48                 account: state.account,
49                 accountType: state.accountType,
50                 types: state.types,
51                 sdnOptions: state.sdnOptions,
52                 savedData: state.savedData,
53                 userProfile: state.userProfile
54             })
55         }
56     }
57     componentWillMount() {
58         this.state = this.props.store.getState();
59         this.props.store.listen(this.storeListener);
60     }
61     componentWillUpdate(nextProps, nextState, nextContext) {
62         if(!_isEmpty(nextContext.userProfile) && !nextState.userProfile) {
63             this.props.store.getTransientAccountForUser(nextContext.userProfile)
64         }
65     }
66     componentWillReceiveProps(nextProps) {
67         if(JSON.stringify(nextProps.params) != JSON.stringify(this.props.params)){
68               this.setUp(nextProps);
69         }
70     }
71     componentWillUnmount() {
72         console.log('unmounting')
73         // this.setState({account: null, accountType: null, types: []})
74         this.props.store.unlisten(this.storeListener);
75     }
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);
82             } else {
83                 this.props.store.viewAccount({type: props.params.type, name: props.params.name});
84             }
85
86         } else {
87             this.props.store.setAccountTemplate(props.params.type, null, SD);
88         }
89     }
90     create(e) {
91         e.preventDefault();
92         var self = this;
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");
97             return;
98         } else {
99             if(!wasAllDetailsFilled(Account)) {
100                 self.props.flux.actions.global.showNotification("Please fill all account details");
101                 return;
102             }
103         }
104
105         let newAccount = _cloneDeep(removeTrailingWhitespace(Account));
106
107         delete newAccount.params;
108
109         newAccount.nestedParams &&
110         newAccount.nestedParams['container-name'] &&
111         delete newAccount[newAccount.nestedParams['container-name']];
112
113         delete newAccount.nestedParams;
114
115         if(AccountType == 'resource-orchestrator') {
116             newAccount['ro-account-type'] = newAccount['account-type'] || newAccount['ro-account-type'];
117             delete newAccount['account-type'];
118         }
119         if(AccountType == 'cloud' && self.props.vduInstanceTimeout != '') {
120             newAccount['vdu-instance-timeout'] = self.props.vduInstanceTimeout;
121         }
122         this.props.flux.actions.global.showScreenLoader();
123         this.props.store.create(newAccount, AccountType).then(
124             function() {
125                 self.props.router.push({pathname:'accounts'});
126                 self.props.flux.actions.global.hideScreenLoader.defer();
127             },
128             function(error) {
129                 self.props.flux.actions.global.showNotification(error);
130                 self.props.flux.actions.global.hideScreenLoader.defer();
131             }
132         );
133     }
134     update(e) {
135         e.preventDefault();
136         var self = this;
137         var Account = this.state.account;
138         let AccountType = this.state.accountType;
139
140         if(!wasAllDetailsFilled(Account)) {
141             self.props.flux.actions.global.showNotification("Please fill all account details");
142             return;
143         }
144
145         if(AccountType == 'cloud' && self.props.vduInstanceTimeout != '') {
146             Account['vdu-instance-timeout'] = self.props.vduInstanceTimeout;
147         }
148         this.props.flux.actions.global.showScreenLoader();
149         this.props.store.update(Account, AccountType).then(
150             function() {
151                 self.props.router.push({pathname:'accounts'});
152                 self.props.flux.actions.global.hideScreenLoader();
153             },
154             function(error){
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);
160             }
161         );
162     }
163     cancel = (e) => {
164         e.preventDefault();
165         e.stopPropagation();
166         this.props.flux.actions.global.handleCancelAccount();
167         this.props.router.push({pathname:'accounts'});
168     }
169     handleDelete = () => {
170         let self = this;
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(
176                 function() {
177                     self.props.flux.actions.global.hideScreenLoader();
178                     self.props.router.push({pathname:'accounts'});
179                 },
180                 function(error){
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);
186                 }
187             );
188         }
189     }
190     handleNameChange(event) {
191        this.props.store.handleNameChange(event);
192     }
193     handleAccountTypeChange(node, isRo, event) {
194         this.props.store.handleAccountTypeChange(node, isRo, event);
195     }
196     handleSelectSdnAccount = (e) => {
197         var tmp = this.state.account;
198         if(e) {
199             tmp['sdn-account'] = e;
200         } else {
201             if(tmp['sdn-account']) {
202                 delete tmp['sdn-account'];
203             }
204         }
205         console.log(e, tmp)
206     }
207     updateVduInstanceTimeout(event) {
208         this.props.store.updateVduTimeout(event)
209     }
210     preventDefault = (e) => {
211         e.preventDefault();
212         e.stopPropagation();
213     }
214     evaluateSubmit = (e) => {
215         if (e.keyCode == 13) {
216             if (this.props.params.name != 'create') {
217                 this.update(e);
218             } else {
219                 this.create(e);
220             }
221             e.preventDefault();
222             e.stopPropagation();
223         }
224     }
225
226     render() {
227         let self = this;
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 || ''} />;
233         let params = null;
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';
240         var buttons;
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;
246         if (!isEdit) {
247             buttons = [
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" />
250             ]
251             for (var i = 0; i < Types.length; i++) {
252                 var node = Types[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'])}/>
260                     </div>
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}
262                   </label>
263                 )
264             }
265             selectAccount = (
266                 <div className="accountForm">
267                     <h3 className="accountForm-title">Select Account Type</h3>
268                     <div className="select-type accountForm-content" >
269                         {selectAccountStack}
270                     </div>
271                 </div>
272             );
273         } else {
274             selectAccount = null
275         }
276
277         //Cloud Account: SDN Account Option
278         let sdnAccounts = null;
279         if( (AccountType == 'cloud') ) {
280             if ( !isEdit && (this.state.sdnOptions.length > 1 )) {
281                 sdnAccounts = (
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} />
286                         </div>
287
288                     </div>
289                 );
290             }
291             if(isEdit && Account['sdn-account']) {
292                 sdnAccounts = ( <div className="associateSdnAccount">SDN Account: {Account['sdn-account']} </div>)
293             }
294         }
295          //END Cloud Account: SDN Account Option
296
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}
303                         placeholder={300}
304                         />
305
306          //
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];
314                 var value = ""
315                 if(isRo) {
316                     if (Account[Account['ro-account-type']] && Account[Account['ro-account-type']][node.ref]) {
317                         value = Account[Account['ro-account-type']][node.ref]
318                     }
319                 } else  {
320                     if (Account[Account['account-type']] && Account[Account['account-type']][node.ref]) {
321                         value = Account[Account['account-type']][node.ref]
322                     }
323                 }
324                 if (this.props.edit && Account.params) {
325                     value = Account.params[node.ref];
326                 }
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( '*' );
331                     }
332                     paramsStack.push(
333                         <TextInput
334                             key={node.label}
335                             className="accountForm-input"
336                             label={node.label}
337                             required={!node.optional}
338                             onChange={this.props.store.handleParamChange(node, isRo)}
339                             value={displayValue}
340                             readonly={self.props.readonly}
341                             type="password"
342                         />
343                     );
344                 } else {
345                     paramsStack.push(
346                         <TextInput
347                             key={node.label}
348                             className="accountForm-input"
349                             label={node.label}
350                             required={!node.optional}
351                             onChange={this.props.store.handleParamChange(node, isRo)}
352                             value={value}
353                             readonly={self.props.readonly}
354                         />
355                     );
356                 }
357             }
358
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];
365                     var value = ""
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];
368                     }
369                     if (node.optional) {
370                         optionalField = <span className="optional">Optional</span>;
371                     }
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}/>
376                     //     </label>
377                     // );
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( '*' );
382                         }
383                         nestedParamsStack.push(
384                               <TextInput
385                                 key={node.label}
386                                 label={node.label}
387                                 required={!node.optional}
388                                 className="create-fleet-pool-input"
389                                 type="password"
390                                 onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)}
391                                 value={displayValue}
392                                 readonly={self.props.readonly}
393                                 />
394                         );
395                     } else {
396                             nestedParamsStack.push(
397                               <TextInput
398                                 key={node.label}
399                                 label={node.label}
400                                 required={!node.optional}
401                                 className="create-fleet-pool-input"
402                                 type="text"
403                                 onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)}
404                                 value={value}
405                                 readonly={self.props.readonly}
406                                 />
407                         );
408                     }
409                 }
410             }
411
412             params = (
413                 <li className="create-fleet-pool accountForm">
414                     <h3  className="accountForm-title"> {isEdit ? 'Update' : 'Enter'} Account Details</h3>
415                     <div className="accountForm-content">
416                         {paramsStack}
417                         <div className="accountForm-nestedParams">
418                         {nestedParamsStack}
419                         </div>
420                     </div>
421                 </li>
422             )
423         } else {
424             params = (
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>
428                 </li>
429             )
430         }
431
432         // This section builds elements that only show up in the edit page.
433         if (isEdit) {
434             name = <label>{Account.name}</label>;
435             buttons = [
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" />
439             ];
440             resfreshStatus = Account['connection-status'] ? (
441                 <div className="accountForm">
442                     <div className="accountForm-title accountForm-title--edit">
443                         Connection Status
444                     </div>
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()}
449                         </div>
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" />
452                         }
453                     </div>
454                     {
455                         (Account['connection-status'] && Account['connection-status'].status && Account['connection-status'].status.toUpperCase()) === 'FAILURE' ?
456                         displayFailureMessage(Account['connection-status'].details) : null
457                     }
458                 </div>
459             ) : null;
460
461         }
462
463         var html = (
464
465               <form className="app-body create Accounts"  onSubmit={this.preventDefault} onKeyDown={this.evaluateSubmit}>
466                 <div className="noticeSubText noticeSubText_right">
467                     * required
468                 </div>
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'}}>
472                         {name}
473                         { isEdit ?
474                             (
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']]}
477                                 </div>)
478                             : null
479                         }
480                     </div>
481                      {
482                         AccountType == 'cloud' ? (
483                                 <div className="accountForm-content">
484                                     {setVduTimeout}
485                                 </div>
486                             )
487                         : null
488                     }
489                 </div>
490
491                   {selectAccount}
492                   {sdnAccounts}
493                   {resfreshStatus}
494                   {cloudResourcesStateHTML}
495                   <ol className="flex-row">
496                       {params}
497                   </ol>
498                   <div className="form-actions">
499                       {!self.props.readonly ? buttons : null}
500                   </div>
501               </form>
502         )
503         return Types.length ? html : null;
504     }
505 }
506
507 Account.contextTypes = {
508     router: React.PropTypes.object,
509     userProfile: React.PropTypes.object
510 }
511
512 function displayFailureMessage(msg) {
513     return (
514         <div className="accountForm-content" style={{maxWidth: '600px'}}>
515             <div style={{paddingBottom: '1rem'}}>Details:</div>
516             <div>
517                 {msg}
518             </div>
519
520         </div>
521     )
522 }
523
524 class SelectOption extends React.Component {
525   constructor(props){
526     super(props);
527   }
528   handleOnChange = (e) => {
529     this.props.onChange(JSON.parse(e.target.value));
530   }
531   render() {
532     let html;
533     html = (
534       <select className={this.props.className} onChange={this.handleOnChange}>
535         {
536           this.props.options.map(function(op, i) {
537             return <option key={i} value={JSON.stringify(op.value)}>{op.label}</option>
538           })
539         }
540       </select>
541     );
542     return html;
543   }
544 }
545 SelectOption.defaultProps = {
546   options: [],
547   onChange: function(e) {
548     console.dir(e)
549   }
550 }
551
552 function wasAllDetailsFilled(Account) {
553     var type = Account['account-type'] || Account['ro-account-type'];
554     var params = Account.params;
555
556     if(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) {
561                     return false;
562                 }
563             }
564         }
565     }
566
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) {
573                     return false;
574                 }
575             }
576         }
577     }
578     return true;
579 }
580
581 function removeTrailingWhitespace(Account) {
582             var type = Account['account-type'] || Account['ro-account-type'];
583             var params = Account.params;
584
585             if(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();
590                     }
591                 }
592             }
593
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();
601                     }
602                 }
603             }
604             return Account;
605 }
606
607 export default SkyquakeComponent(Account)