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