Account page RBAC
[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 _ from 'lodash';
22 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
23 import Crouton from 'react-crouton';
24 import TextInput from 'widgets/form_controls/textInput.jsx';
25 import {AccountConnectivityStatus} from '../account_sidebar/accountSidebar.jsx';
26
27 import 'style/common.scss';
28 import './account.scss';
29 class Account extends React.Component {
30     constructor(props) {
31         super(props);
32         this.state = {};
33         this.state.account = {};
34     }
35     storeListener = (state) => {
36         if(!state.account) {
37             this.setUp(this.props)
38         }
39         state.account && this.setState({account: state.account,accountType: state.accountType, types: state.types, sdnOptions: state.sdnOptions})
40     }
41     componentWillMount() {
42         this.props.store.listen(this.storeListener);
43         this.setUp(this.props);
44     }
45     componentWillReceiveProps(nextProps) {
46         if(JSON.stringify(nextProps.params) != JSON.stringify(this.props.params)){
47               this.setUp(nextProps);
48         }
49     }
50     componentWillUnmount() {
51         this.props.store.unlisten(this.storeListener);
52     }
53     setUp(props){
54         if(props.params.name != 'create') {
55             this.props.store.viewAccount({type: props.params.type, name: props.params.name});
56         } else {
57             this.props.store.setAccountTemplate(props.params.type);
58         }
59     }
60     create(e) {
61         e.preventDefault();
62         var self = this;
63         var Account = this.state.account;
64         let AccountType = this.state.accountType;
65         if (Account.name == "") {
66             self.props.flux.actions.global.showNotification("Please give the account a name");
67             return;
68         } else {
69             var type = Account['account-type'];
70             var params = Account.params;
71
72             if(params) {
73                 for (var i = 0; i < params.length; i++) {
74                     var param = params[i].ref;
75                     if (typeof(Account[type]) == 'undefined' || typeof(Account[type][param]) == 'undefined' || Account[type][param] == "") {
76                         if (!params[i].optional) {
77                             self.props.flux.actions.global.showNotification("Please fill all account details");
78                             return;
79                         }
80                     }
81                 }
82             }
83
84             let nestedParams = Account.nestedParams && Account.nestedParams;
85             if (nestedParams && nestedParams.params) {
86                 for (let i = 0; i < nestedParams.params.length; i++) {
87                     let nestedParam = nestedParams.params[i].ref;
88                     if (typeof(Account[type]) == 'undefined' || typeof(Account[type][nestedParams['container-name']][nestedParam]) == 'undefined' || Account[type][nestedParams['container-name']][nestedParam] == "") {
89                         if (!nestedParams.params[i].optional) {
90                             self.props.flux.actions.global.showNotification("Please fill all account details");
91                             return;
92                         }
93                     }
94                 }
95             }
96         }
97
98         let newAccount = _.cloneDeep(removeTrailingWhitespace(Account));
99         delete newAccount.params;
100         newAccount.nestedParams &&
101             newAccount.nestedParams['container-name'] &&
102             delete newAccount[newAccount.nestedParams['container-name']];
103         delete newAccount.nestedParams;
104
105         this.props.flux.actions.global.showScreenLoader();
106         this.props.store.create(newAccount, AccountType).then(function() {
107             self.props.router.push({pathname:'accounts'});
108             self.props.flux.actions.global.hideScreenLoader.defer();
109         },
110          function() {
111             self.props.flux.actions.global.showNotification("There was an error creating your account. Please contact your system administrator.");
112             self.props.flux.actions.global.hideScreenLoader.defer();
113          });
114     }
115     update(e) {
116         e.preventDefault();
117         var self = this;
118         var Account = this.state.account;
119         let AccountType = this.state.accountType;
120         this.props.flux.actions.global.showScreenLoader();
121         this.props.store.update(Account, AccountType).then(function() {
122             self.props.router.push({pathname:'accounts'});
123              self.props.flux.actions.global.hideScreenLoader();
124         },
125         function() {
126
127         });
128     }
129     cancel = (e) => {
130         e.preventDefault();
131         e.stopPropagation();
132         this.props.router.push({pathname:'accounts'});
133     }
134     handleDelete = () => {
135         let self = this;
136         let msg = 'Preparing to delete "' + self.state.account.name + '"' +
137         ' Are you sure you want to delete this ' + self.state.accountType + ' account?"';
138         if (window.confirm(msg)) {
139             this.props.store.delete(self.state.accountType, self.state.account.name).then(function() {
140                 self.props.flux.actions.global.hideScreenLoader();
141                 self.props.router.push({pathname:'accounts'});
142             }, function(){
143                 // self.props.flux.actions.global.hideScreenLoader.defer();
144                 // console.log('Delete Account Fail');
145             });
146         } else {
147            self.props.flux.actions.global.hideScreenLoader();
148         }
149     }
150     handleNameChange(event) {
151        this.props.store.handleNameChange(event);
152     }
153     handleAccountTypeChange(node, event) {
154         this.props.store.handleAccountTypeChange(node, event);
155     }
156     handleSelectSdnAccount = (e) => {
157         var tmp = this.state.account;
158         if(e) {
159             tmp['sdn-account'] = e;
160         } else {
161             if(tmp['sdn-account']) {
162                 delete tmp['sdn-account'];
163             }
164         }
165         console.log(e, tmp)
166     }
167     preventDefault = (e) => {
168         e.preventDefault();
169         e.stopPropagation();
170     }
171     evaluateSubmit = (e) => {
172         if (e.keyCode == 13) {
173             if (this.props.edit) {
174                 this.update(e);
175             } else {
176                 this.create(e);
177             }
178             e.preventDefault();
179             e.stopPropagation();
180         }
181     }
182
183     render() {
184         let self = this;
185         let {store, ...props} = this.props;
186         // This section builds elements that only show up on the create page.
187         // var name = <label>Name <input type="text" onChange={this.handleNameChange.bind(this)} style={{'textAlign':'left'}} /></label>;
188         var name = <TextInput label="Name"  onChange={this.handleNameChange.bind(this)} required={true} />;
189         let params = null;
190         let selectAccount = null;
191         let resfreshStatus = null;
192         let Account = this.state.account;
193         // AccountType is for the view, not the data account-type value;
194         let AccountType = this.state.accountType;
195         let Types = this.state.types;
196         let isEdit = this.props.params.name != 'create';
197         var buttons;
198         let cloudResources = Account['cloud-resources-state'] && Account['cloud-resources-state'][Account['account-type']];
199         let cloudResourcesStateHTML = null;
200
201         // Account Type Radio
202         var selectAccountStack = [];
203         if (!isEdit) {
204             buttons = [
205                 <Button key="0" onClick={this.cancel} className="cancel light" label="Cancel"></Button>,
206                 <Button key="1" role="button" onClick={this.create.bind(this)} className="save dark" label="Save" />
207             ]
208             for (var i = 0; i < Types.length; i++) {
209                 var node = Types[i];
210                 var isSelected = (Account['account-type'] == node['account-type']);
211                 selectAccountStack.push(
212                   <label key={i} className={"accountSelection " + (isSelected ? "accountSelection--isSelected" : "")}>
213                     <div className={"accountSelection-overlay" + (isSelected ? "accountSelection-overlay--isSelected" : "")}></div>
214                     <div className="accountSelection-imageWrapper">
215                         <img src={store.getImage(node['account-type'])}/>
216                     </div>
217                     <input type="radio" name="account" onChange={this.handleAccountTypeChange.bind(this, node)} defaultChecked={node.name == Types[0].name} value={node['account-type']} />{node.name}
218                   </label>
219                 )
220             }
221             selectAccount = (
222                 <div className="accountForm">
223                     <h3 className="accountForm-title">Select Account Type</h3>
224                     <div className="select-type accountForm-content" >
225                         {selectAccountStack}
226                     </div>
227                 </div>
228             );
229         } else {
230             selectAccount = null
231         }
232
233         //Cloud Account: SDN Account Option
234         let sdnAccounts = null;
235         if( (AccountType == 'cloud') ) {
236             if ( !isEdit && (this.state.sdnOptions.length > 1 )) {
237                 sdnAccounts = (
238                     <div className="associateSdnAccount accountForm">
239                         <h3 className="accountForm-title">Associate SDN Account</h3>
240                         <div className="accountForm-content">
241                             <SelectOption  options={this.state.sdnOptions} onChange={this.handleSelectSdnAccount} />
242                         </div>
243
244                     </div>
245                 );
246             }
247             if(isEdit && Account['sdn-account']) {
248                 sdnAccounts = ( <div className="associateSdnAccount">SDN Account: {Account['sdn-account']} </div>)
249             }
250         }
251          //END Cloud Account: SDN Account Option
252          //
253         // This sections builds the parameters for the account details.
254         if (Account.params) {
255             var paramsStack = [];
256             var optionalField = '';
257             for (var i = 0; i < Account.params.length; i++) {
258                 var node = Account.params[i];
259                 var value = ""
260                 if (Account[Account['account-type']]) {
261                     value = Account[Account['account-type']][node.ref]
262                 }
263                 if (this.props.edit && Account.params) {
264                     value = Account.params[node.ref];
265                 }
266                 paramsStack.push(
267                     <TextInput
268                         key={node.label}
269                         className="accountForm-input"
270                         label={node.label}
271                         required={!node.optional}
272                         onChange={this.props.store.handleParamChange(node)}
273                         value={value}
274                         readonly={self.props.readonly}
275
276                         />
277                 );
278             }
279
280             let nestedParamsStack = null;
281             if (Account.nestedParams) {
282                 nestedParamsStack = [];
283                 var optionalField = '';
284                 for (var i = 0; i < Account.nestedParams.params.length; i++) {
285                     var node = Account.nestedParams.params[i];
286                     var value = ""
287                     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]) {
288                         value = Account[Account['account-type']][Account.nestedParams['container-name']][node.ref];
289                     }
290                     if (node.optional) {
291                         optionalField = <span className="optional">Optional</span>;
292                     }
293                     // nestedParamsStack.push(
294                     //     <label key={node.label}>
295                     //       <label className="create-fleet-pool-params">{node.label} {optionalField}</label>
296                     //       <input className="create-fleet-pool-input" type="text" onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)} value={value}/>
297                     //     </label>
298                     // );
299                     nestedParamsStack.push(
300                           <TextInput
301                             key={node.label}
302                             label={node.label}
303                             required={!node.optional}
304                             className="create-fleet-pool-input"
305                             type="text"
306                             onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)}
307                             value={value}
308                             readonly={self.props.readonly}
309                             />
310                     );
311                 }
312             }
313
314             params = (
315                 <li className="create-fleet-pool accountForm">
316                     <h3  className="accountForm-title"> {isEdit ? 'Update' : 'Enter'} Account Details</h3>
317                     <div className="accountForm-content">
318                         {paramsStack}
319                         <div className="accountForm-nestedParams">
320                         {nestedParamsStack}
321                         </div>
322                     </div>
323                 </li>
324             )
325         } else {
326             params = (
327                 <li className="create-fleet-pool accountForm">
328                     <h3 className="accountForm-title"> {isEdit ? 'Update' : 'Enter'}</h3>
329                     <label style={{'marginLeft':'17px', color:'#888'}}>No Details Required</label>
330                 </li>
331             )
332         }
333
334         // This section builds elements that only show up in the edit page.
335         if (isEdit) {
336             name = <label>{Account.name}</label>;
337             buttons = [
338                 <Button key="2" onClick={this.handleDelete} className="light" label="Remove Account" />,
339                 <Button key="3" onClick={this.cancel} className="light" label="Cancel" />,
340                 <Button key="4" role="button" onClick={this.update.bind(this)} className="update dark" label="Update" />
341             ];
342             resfreshStatus = Account['connection-status'] ? (
343                 <div className="accountForm">
344                     <div className="accountForm-title accountForm-title--edit">
345                         Connection Status
346                     </div>
347                     <div className="accountForm-content" style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
348                         <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
349                             <AccountConnectivityStatus status={Account['connection-status'].status} />
350                             {Account['connection-status'] && Account['connection-status'].status &&  Account['connection-status'].status.toUpperCase()}
351                         </div>
352                             <Button is-disabled={self.props.readonly} className="refreshList light" onClick={this.props.store.refreshAccount.bind(this, Account.name, AccountType)} label="REFRESH STATUS"></Button>
353                     </div>
354                     {
355                         (Account['connection-status'] && Account['connection-status'].status && Account['connection-status'].status.toUpperCase()) === 'FAILURE' ?
356                         displayFailureMessage(Account['connection-status'].details) : null
357                     }
358                 </div>
359             ) : null;
360             // cloudResourcesStateHTML = (
361             //     <div className="accountForm">
362             //         <h3 className="accountForm-title">Resources Status</h3>
363             //         <div className="accountForm-content" >
364             //         <ul>
365             //             {
366             //                 cloudResources && props.AccountMeta.resources[Account['account-type']].map(function(r, i) {
367
368             //                     return (
369             //                         <li key={i}>
370             //                             {r}: {cloudResources[r]}
371             //                         </li>
372             //                     )
373             //                 }) || 'No Additional Resources'
374             //             }
375             //         </ul>
376             //         </div>
377             //     </div>
378             // )
379         }
380
381         var html = (
382
383               <form className="app-body create Accounts"  onSubmit={this.preventDefault} onKeyDown={this.evaluateSubmit}>
384                 <div className="noticeSubText noticeSubText_right">
385                     * required
386                 </div>
387                 <div className="associateSdnAccount accountForm">
388                     <h3 className="accountForm-title">Account</h3>
389                     <div className="accountForm-content" style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
390                         <h4 style={{flex: '1'}}>{name}</h4>
391                         { isEdit ?
392                             (
393                                 <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
394                                     <img src={store.getImage(Account['account-type'])}/> {props.AccountMeta.labelByType[Account['account-type']]}
395                                 </div>)
396                             : null
397                         }
398                     </div>
399                 </div>
400
401                   {selectAccount}
402                   {sdnAccounts}
403                   {resfreshStatus}
404                   {cloudResourcesStateHTML}
405                   <ol className="flex-row">
406                       {params}
407                   </ol>
408                   <div className="form-actions">
409                       {!self.props.readonly ? buttons : null}
410                   </div>
411               </form>
412         )
413         return html;
414     }
415 }
416
417 Account.contextTypes = {
418     router: React.PropTypes.object,
419     userProfile: React.PropTypes.object
420 }
421
422 function displayFailureMessage(msg) {
423     return (
424         <div className="accountForm-content" style={{maxWidth: '600px'}}>
425             <div style={{paddingBottom: '1rem'}}>Details:</div>
426             <div>
427                 {msg}
428             </div>
429
430         </div>
431     )
432 }
433
434 class SelectOption extends React.Component {
435   constructor(props){
436     super(props);
437   }
438   handleOnChange = (e) => {
439     this.props.onChange(JSON.parse(e.target.value));
440   }
441   render() {
442     let html;
443     html = (
444       <select className={this.props.className} onChange={this.handleOnChange}>
445         {
446           this.props.options.map(function(op, i) {
447             return <option key={i} value={JSON.stringify(op.value)}>{op.label}</option>
448           })
449         }
450       </select>
451     );
452     return html;
453   }
454 }
455 SelectOption.defaultProps = {
456   options: [],
457   onChange: function(e) {
458     console.dir(e)
459   }
460 }
461
462 function removeTrailingWhitespace(Account) {
463              var type = Account['account-type'];
464             var params = Account.params;
465
466             if(params) {
467                 for (var i = 0; i < params.length; i++) {
468                     var param = params[i].ref;
469                     if(typeof(Account[type][param]) == 'string') {
470                         Account[type][param] = Account[type][param].trim();
471                     }
472                 }
473             }
474
475             let nestedParams = Account.nestedParams;
476             if (nestedParams && nestedParams.params) {
477                 for (let i = 0; i < nestedParams.params.length; i++) {
478                     let nestedParam = nestedParams.params[i].ref;
479                     let nestedParamValue = Account[type][nestedParams['container-name']][nestedParam];
480                     if (typeof(nestedParamValue) == 'string') {
481                         Account[type][nestedParams['container-name']][nestedParam] = nestedParamValue.trim();
482                     }
483                 }
484             }
485             return Account;
486 }
487
488 export default SkyquakeComponent(Account)