update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / user_management / src / dashboard / dashboard.jsx
1 /*
2  * STANDARD_RIFT_IO_COPYRIGHT
3  */
4
5 import React from 'react';
6 import ReactDOM from 'react-dom';
7 import AppHeader from 'widgets/header/header.jsx';
8 import UserManagementStore from './userMgmtStore.js';
9 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
10 import 'style/layout.scss';
11 import './userMgmt.scss';
12 import { Panel, PanelWrapper } from 'widgets/panel/panel';
13 import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
14
15 import TextInput from 'widgets/form_controls/textInput.jsx';
16 import Input from 'widgets/form_controls/input.jsx';
17 import Button, { ButtonGroup } from 'widgets/button/sq-button.jsx';
18 import SelectOption from 'widgets/form_controls/selectOption.jsx';
19 import 'widgets/form_controls/formControls.scss';
20 import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
21 import imgRemove from '../../node_modules/open-iconic/svg/trash.svg';
22 import { merge } from 'lodash';
23
24 import ROLES from 'utils/roleConstants.js';
25 const PLATFORM = ROLES.PLATFORM;
26
27 class UserManagementDashboard extends React.Component {
28     constructor(props) {
29         super(props);
30         this.Store = this.props.flux.stores.hasOwnProperty('UserManagementStore') ? this.props.flux.stores.UserManagementStore : this.props.flux.createStore(UserManagementStore, 'UserManagementStore');
31         this.state = this.Store.getState();
32         this.actions = this.state.actions;
33     }
34     componentDidUpdate() {
35         let self = this;
36         ReactDOM.findDOMNode(this.UserList).addEventListener('transitionend', this.onTransitionEnd, false);
37         setTimeout(function () {
38             let element = self[`user-ref-${self.state.activeIndex}`]
39             element && !isElementInView(element) && element.scrollIntoView({ block: 'end', behavior: 'smooth' });
40         })
41     }
42     componentWillMount() {
43         this.Store.listen(this.updateState);
44         this.Store.getUsers();
45     }
46     componentWillUnmount() {
47         this.Store.unlisten(this.updateState);
48     }
49     updateState = (state) => {
50         this.setState(state);
51     }
52     updateInput = (key, e) => {
53         let property = key;
54         this.actions.handleUpdateInput({
55             [property]: e.target.value
56         })
57     }
58     platformChange = (platformRole, e) => {
59         this.actions.handlePlatformRoleUpdate(platformRole, e.currentTarget.checked);
60     }
61     addProjectRole = (e) => {
62         this.actions.handleAddProjectItem();
63     }
64     removeProjectRole = (i, e) => {
65         this.actions.handleRemoveProjectItem(i);
66     }
67     updateProjectRole = (i, e) => {
68         this.actions.handleUpdateProjectRole(i, e)
69     }
70     addUser = () => {
71         this.actions.handleAddUser();
72     }
73     viewUser = (un, index) => {
74         this.actions.viewUser(un, index);
75     }
76     editUser = () => {
77         this.actions.editUser(false);
78     }
79     cancelEditUser = () => {
80         this.actions.editUser(true)
81     }
82     closePanel = () => {
83         this.actions.handleCloseUserPanel();
84     }
85     // updateUser = (e) => {
86     //     e.preventDefault();
87     //     e.stopPropagation();
88
89     //     this.Store.updateUser();
90     // }
91     deleteUser = (e) => {
92         e.preventDefault();
93         e.stopPropagation();
94         if (confirm('Are you sure you want to delete this user?')) {
95             this.Store.deleteUser({
96                 'user-name': this.state['user-name'],
97                 'user-domain': this.state['user-domain']
98             });
99         }
100
101     }
102     createUser = (e) => {
103         e.preventDefault();
104         e.stopPropagation();
105         if (!this.state['user-name'] || (this.state['user-name'].trim() == "")) {
106             this.props.actions.showNotification('Please enter a valid username');
107             return;
108         }
109
110         if((this.state['user-domain'].toLowerCase() == 'system') && !this.state['new-password'] || (this.state['new-password'].trim == "")) {
111             this.props.actions.showNotification('You must enter a password');
112             return;
113         }
114         if((this.state['user-domain'].toLowerCase() == 'system') && this.state['new-password'] != this.state['confirm-password']) {
115             this.props.actions.showNotification('Passwords do not match');
116             return;
117         } else {
118             let isDisabled = {};
119             if (this.state.disabled == "TRUE") {
120                 isDisabled = {
121                     disabled: [null]
122                 }
123             }
124             this.Store.createUser(_.merge({
125                 'user-name': this.state['user-name'],
126                 'user-domain': this.state['user-domain'],
127                 'password': this.state['new-password'],
128                 // 'confirm-password': this.state['confirm-password']
129             }, isDisabled));
130         }
131     }
132     updateUser = (e) => {
133         let self = this;
134         e.preventDefault();
135         e.stopPropagation();
136         let validatedPasswords = validatePasswordFields(this.state);
137         if (validatedPasswords) {
138             let isDisabled = {};
139             let password = {};
140             if (self.state.disabled == "TRUE") {
141                 isDisabled = {
142                     disabled: [null]
143                 }
144             }
145             if (this.state['new-password'] != '') {
146                 password = { 'password': this.state['new-password'] }
147             } else {
148                 password = {
149                     'password': this.state.currentPassword
150                 }
151             }
152             this.Store.updateUser(_.merge({
153                 'user-name': this.state['user-name'],
154                 'user-domain': this.state['user-domain'],
155                 'ui-state': this.context.userProfile.data['ui-state']
156             }, _.merge(isDisabled, password)));
157         }
158         function validatePasswordFields(state) {
159             let newOne = state['new-password'];
160             let confirmOne = state['confirm-password'];
161             if (!newOne && !confirmOne) {
162                 // self.props.actions.showNotification('Please fill in all fields.');
163                 return true;
164             }
165             if (newOne != confirmOne) {
166                 self.props.actions.showNotification('Passwords do not match');
167                 return false;
168             }
169             return {
170                 'new-password': newOne,
171                 'confirm-password': confirmOne
172             }
173         }
174     }
175     evaluateSubmit = (e) => {
176         if (e.keyCode == 13) {
177             if (this.state.isEdit) {
178                 this.updateUser(e);
179             } else {
180                 this.createUser(e);
181             }
182             e.preventDefault();
183             e.stopPropagation();
184         }
185     }
186     onTransitionEnd = (e) => {
187         this.actions.handleHideColumns(e);
188         console.log('transition end')
189     }
190     disableChange = (e) => {
191         let value = e.target.value;
192         value = value.toUpperCase();
193         this.actions.handleDisabledChange(value);
194     }
195     render() {
196         let self = this;
197         let html;
198         let props = this.props;
199         let state = this.state;
200         let passwordSectionHTML = null;
201         let formButtonsHTML = (
202             <ButtonGroup className="buttonGroup">
203                 <Button label="EDIT" type="submit" onClick={this.editUser} />
204             </ButtonGroup>
205         );
206         if(!this.state.isReadOnly) {
207             passwordSectionHTML = (state['user-domain'].toLowerCase() == 'system') ?
208                 ( this.state.isEdit ?
209                     (
210                         <FormSection title="PASSWORD CHANGE">
211                             <Input label="NEW PASSWORD" type="password" value={state['new-password']}  onChange={this.updateInput.bind(null, 'new-password')}/>
212                             <Input label="REPEAT NEW PASSWORD" type="password"  value={state['confirm-password']}  onChange={this.updateInput.bind(null, 'confirm-password')}/>
213                         </FormSection>
214                     ) :
215                     (
216                         <FormSection title="CREATE PASSWORD">
217                             <Input label="CREATE PASSWORD" type="password" value={state.newPassword}  onChange={this.updateInput.bind(null, 'new-password')}/>
218                             <Input label="REPEAT PASSWORD" type="password"  value={state.repeatNewPassword}  onChange={this.updateInput.bind(null, 'confirm-password')}/>
219                         </FormSection>
220                     )
221                 )
222                 : null;
223             formButtonsHTML = (
224                 state.isEdit ?
225                     (
226                         <ButtonGroup className="buttonGroup">
227                             <Button label="Delete" onClick={this.deleteUser} />
228                             <Button label="Cancel" onClick={this.cancelEditUser} />
229                             <Button label="Update" type="submit" onClick={this.updateUser} />
230                         </ButtonGroup>
231                     )
232                     : (
233                         <ButtonGroup className="buttonGroup">
234                             <Button label="Cancel" onClick={this.closePanel} />
235                             <Button label="Create" type="submit" onClick={this.createUser} />
236                         </ButtonGroup>
237                     )
238             )
239         }
240         html = (
241             <PanelWrapper column>
242                 <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} >
243                     <AppHeader nav={[{ name: 'USER MANAGEMENT' }, { name: 'PLATFORM ROLE MANAGEMENT', onClick: this.context.router.push.bind(this, { pathname: '/platform' }) }]} />
244                 </SkyquakeRBAC>
245                 <PanelWrapper className={`row userManagement ${!this.state.userOpen ? 'userList-open' : ''}`} style={{ 'flexDirection': 'row' }} >
246                     <PanelWrapper ref={(div) => { this.UserList = div }} className={`column userList expanded ${this.state.userOpen ? 'collapsed ' : ' '} ${this.state.hideColumns ? 'hideColumns ' : ' '}`}>
247                         <Panel title="User List" style={{ marginBottom: 0 }} no-corners>
248                             <div className="tableRow tableRow--header">
249                                 <div className="userName">
250                                     Username
251                                 </div>
252                                 <div>
253                                     Domain
254                                 </div>
255                                 <div>
256                                     Status
257                                 </div>
258                             </div>
259                             {state.users && state.users.map((u, k) => {
260                                 let platformRoles = [];
261                                 for (let role in u.platformRoles) {
262                                     platformRoles.push(<div>{`${role}: ${u.platformRoles[role]}`}</div>)
263                                 }
264                                 return (
265                                     <div ref={(el) => this[`user-ref-${k}`] = el} className={`tableRow tableRow--data ${((self.state.activeIndex == k) && self.state.userOpen) ? 'tableRow--data-active' : ''}`}
266                                         key={k}
267                                         onClick={self.viewUser.bind(null, u, k)}>
268                                         <div
269                                             className={`userName userName-header ${((self.state.activeIndex == k) && self.state.userOpen) ? 'activeUser' : ''}`}
270                                         >
271                                             {u['user-name']}
272                                         </div>
273                                         <div>
274                                             {u['user-domain']}
275                                         </div>
276                                         <div>
277                                             {u['disabled'] ? "DISABLED" : "ENABLED"}
278                                         </div>
279                                     </div>
280                                 )
281                             })}
282                         </Panel>
283                         <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} className="rbacButtonGroup">
284                             <ButtonGroup className="buttonGroup">
285                                 <Button label="Add User" onClick={this.addUser} />
286                             </ButtonGroup>
287                         </SkyquakeRBAC>
288                     </PanelWrapper>
289                     <PanelWrapper onKeyUp={this.evaluateSubmit}
290                         className={`userAdmin column`}>
291                         <Panel
292                             title={state.isEdit ? state['user-name'] : 'Create User'}
293                             style={{ marginBottom: 0 }}
294                             hasCloseButton={this.closePanel}
295                             no-corners>
296                             <FormSection title="USER INFO" className="userInfo">
297                             {
298                                 (!state.isEditUser ||  state.isReadOnly) ?
299                                 <Input className="userInfo-section" readonly={state.isReadOnly || this.state.isEdit}  label="Username" value={state['user-name']} onChange={this.updateInput.bind(null, 'user-name')} />
300                                 : null
301                             }
302                                 <Input className="userInfo-section" readonly={state.isReadOnly || this.state.isEdit} label="Domain" value={state['user-domain']}  onChange={this.updateInput.bind(null, 'user-domain')}></Input>
303                                 <Input className="userInfo-section"
304                                     type="radiogroup"
305                                     onChange={this.disableChange}
306                                     label="STATUS"
307                                     value={this.state.disabled}
308                                     options={[{ label: "DISABLED", value: "TRUE" }, { label: "ENABLED", value: "FALSE" }]}
309                                     readonly={state.isReadOnly}
310                                     readonlydisplay={this.state.disabled == "TRUE" ? "DISABLED" : "ENABLED"}
311                                 />
312                             </FormSection>
313                             <FormSection title="PLATFORM ROLES" style={{ display: 'none' }}>
314                                 <Input label="Super Admin" onChange={this.platformChange.bind(null, 'super_admin')} checked={state.platformRoles.super_admin} type="checkbox" />
315                                 <Input label="Platform Admin" onChange={this.platformChange.bind(null, 'platform_admin')} checked={state.platformRoles.platform_admin} type="checkbox" />
316                                 <Input label="Platform Oper" onChange={this.platformChange.bind(null, 'platform_oper')} checked={state.platformRoles.platform_oper} type="checkbox" />
317                             </FormSection>
318                             {
319                                 state.isEdit ?
320                                     <FormSection title="PROJECT ROLES">
321                                         <table className="userProfile-table">
322                                             <thead>
323                                                 <tr>
324                                                     <td>Project</td>
325                                                     <td>Role</td>
326                                                 </tr>
327                                             </thead>
328                                             <tbody>
329                                                 {
330                                                     this.state.projects && this.state.projects.ids && this.state.projects.ids.map((p, i) => {
331                                                         let project = self.state.projects.data[p];
332                                                         let userRoles = [];
333                                                         return (
334                                                             <tr key={i}>
335                                                                 <td>
336                                                                     {p}
337                                                                 </td>
338                                                                 <td>
339                                                                     {
340                                                                         project.map(function (k) {
341                                                                             return <div>{k}</div>
342                                                                         })
343                                                                     }
344                                                                 </td>
345                                                             </tr>
346                                                         )
347                                                     })
348                                                 }
349                                             </tbody>
350                                         </table>
351                                     </FormSection>
352                                     : null
353                             }
354
355                             {passwordSectionHTML}
356
357                         </Panel>
358                         <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} className="rbacButtonGroup">
359                             {formButtonsHTML}
360                         </SkyquakeRBAC>
361                     </PanelWrapper>
362                 </PanelWrapper>
363             </PanelWrapper>
364         );
365         return html;
366     }
367 }
368 // onClick={this.Store.update.bind(null, Account)}
369 UserManagementDashboard.contextTypes = {
370     router: React.PropTypes.object,
371     userProfile: React.PropTypes.object
372 };
373
374 UserManagementDashboard.defaultProps = {
375     userList: [],
376     selectedUser: {}
377 }
378
379 export default SkyquakeComponent(UserManagementDashboard);
380
381
382 function isElementInView(el) {
383     var rect = el && el.getBoundingClientRect() || {};
384
385     return (
386         rect.top >= 0 &&
387         rect.left >= 0 &&
388         rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
389         rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
390     );
391 }
392
393
394 // isReadOnly={state.isReadOnly} disabled={state.disabled} onChange={this.disableChange}
395
396 class isDisabled extends React.Component {
397     constructor(props) {
398         super(props);
399     }
400     render() {
401         let props = this.props;
402         return (<div />)
403     }
404 }
405
406 /**
407  * AddItemFn:
408  */
409 class InputCollection extends React.Component {
410     constructor(props) {
411         super(props);
412         this.collection = props.collection;
413     }
414     buildTextInput(onChange, v, i) {
415         return (
416             <Input
417                 readonly={this.props.readonly}
418                 style={{ flex: '1 1' }}
419                 key={i}
420                 value={v}
421                 onChange={onChange.bind(null, i)}
422             />
423         )
424     }
425     buildSelectOption(initial, options, onChange, v, i) {
426         return (
427             <SelectOption
428                 readonly={this.props.readonly}
429                 key={`${i}-${v.replace(' ', '_')}`}
430                 intial={initial}
431                 defaultValue={v}
432                 options={options}
433                 onChange={onChange.bind(null, i)}
434             />
435         );
436     }
437     showInput() {
438
439     }
440     render() {
441         const props = this.props;
442         let inputType;
443         let className = "InputCollection";
444         if (props.className) {
445             className = `${className} ${props.className}`;
446         }
447         if (props.type == 'select') {
448             inputType = this.buildSelectOption.bind(this, props.initial, props.options, props.onChange);
449         } else {
450             inputType = this.buildTextInput.bind(this, props.onChange)
451         }
452         let html = (
453             <div className="InputCollection-wrapper">
454                 {props.collection.map((v, i) => {
455                     return (
456                         <div key={i} className={className} >
457                             {inputType(v, i)}
458                             {
459                                 props.readonly ? null : <span onClick={props.RemoveItemFn.bind(null, i)} className="removeInput"><img src={imgRemove} />Remove</span>}
460                         </div>
461                     )
462                 })}
463                 {props.readonly ? null : <span onClick={props.AddItemFn} className="addInput"><img src={imgAdd} />Add</span>}
464             </div>
465         );
466         return html;
467     }
468 }
469
470 InputCollection.defaultProps = {
471     input: Input,
472     collection: [],
473     onChange: function (i, e) {
474         console.log(`
475                         Updating with: ${e.target.value}
476                         At index of: ${i}
477                     `)
478     },
479     AddItemFn: function (e) {
480         console.log(`Adding a new item to collection`)
481     },
482     RemoveItemFn: function (i, e) {
483         console.log(`Removing item from collection at index of: ${i}`)
484     }
485 }
486
487 class FormSection extends React.Component {
488     render() {
489         let className = 'FormSection ' + this.props.className;
490         let html = (
491             <div
492                 style={this.props.style}
493                 className={className}
494             >
495                 <div className="FormSection-title">
496                     {this.props.title}
497                 </div>
498                 <div className="FormSection-body">
499                     {this.props.children}
500                 </div>
501             </div>
502         );
503         return html;
504     }
505 }
506
507 FormSection.defaultProps = {
508     className: ''
509 }