Merge "User profile first pass" into projects
authorkashalkar <kiran.kashalkar@riftio.com>
Mon, 3 Apr 2017 16:36:20 +0000 (18:36 +0200)
committerGerrit Code Review <root@osm.etsi.org>
Mon, 3 Apr 2017 16:36:20 +0000 (18:36 +0200)
15 files changed:
skyquake/framework/widgets/form_controls/formControls.scss
skyquake/framework/widgets/skyquake_container/skyquakeComponent.jsx
skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx
skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js
skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx
skyquake/framework/widgets/skyquake_nav/skyquakeNav.scss
skyquake/plugins/project_management/src/dashboard/projectMgmt.scss
skyquake/plugins/project_management/src/dashboard/projectMgmtStore.js
skyquake/plugins/user_management/config.json
skyquake/plugins/user_management/src/dashboard/dashboard.jsx
skyquake/plugins/user_management/src/dashboard/userMgmt.scss
skyquake/plugins/user_management/src/userProfile/userProfile.jsx [new file with mode: 0644]
skyquake/plugins/user_management/src/userProfile/userProfileActions.js [new file with mode: 0644]
skyquake/plugins/user_management/src/userProfile/userProfileSource.js [new file with mode: 0644]
skyquake/plugins/user_management/src/userProfile/userProfileStore.js [new file with mode: 0644]

index 54d69a8..afa8508 100644 (file)
     }
 }
 
+.FormSection {
+    &-title {
+        color: #000;
+        background: lightgray;
+        padding: 0.5rem;
+        border-top: 1px solid #f1f1f1;
+        border-bottom: 1px solid #f1f1f1;
+    }
+    &-body {
+        padding: 0.5rem 0.75rem;
+    }
+    label {
+        -ms-flex: 1 0;
+            flex: 1 0;
+    }
+    /* label {*/
+    /*     display: -ms-flexbox;*/
+    /*     display: flex;*/
+    /*     -ms-flex-direction: column;*/
+    /*     flex-direction: column;*/
+    /*     width: 100%;*/
+    /*     margin: 0.5rem 0;*/
+    /*     -ms-flex-align: start;*/
+    /*     align-items: flex-start;*/
+    /*     -ms-flex-pack: start;*/
+    /*     justify-content: flex-start;*/
+    /* }*/
+    select {
+        font-size: 1rem;
+        min-width: 75%;
+        height: 35px;
+    }
+}
+
+
+
+
+.InputCollection {
+    display:-ms-flexbox;
+    display:flex;
+    -ms-flex-wrap: nowrap;
+        flex-wrap: nowrap;
+    -ms-flex-align: center;
+        align-items: center;
+    button {
+        padding: 0.25rem;
+        height: 1.5rem;
+        font-size: 0.75rem;
+    }
+    select {
+        min-width: 100%;
+    }
+    margin-bottom:0.5rem;
+    &-wrapper {
+
+    }
+}
index 893a4c1..cd489ac 100644 (file)
@@ -7,7 +7,7 @@ export default function(Component) {
         this.actions = context.flux.actions.global;
     }
     render(props) {
-        return <Component {...this.props} router={this.router} actions={this.actions} flux={this.context.flux} />
+        return <Component {...this.props} router={this.router} actions={this.actions} flux={this.context.flux}/>
     }
   }
   SkyquakeComponent.contextTypes = {
index 9d60183..6347c8f 100644 (file)
@@ -40,13 +40,21 @@ export default class skyquakeContainer extends React.Component {
         this.state.eventCenterIsOpen = false;
         this.state.currentPlugin = SkyquakeContainerStore.currentPlugin;
     }
-
+    getChildContext() {
+        return {
+          userProfile: this.state.user
+        };
+    }
+    getUserProfile() {
+        return this.state.user;
+    }
     componentWillMount() {
         let self = this;
 
         Utils.bootstrapApplication().then(function() {
             SkyquakeContainerStore.listen(self.listener);
             SkyquakeContainerStore.getNav();
+            SkyquakeContainerStore.getUserProfile();
             SkyquakeContainerStore.getEventStreams();
         });
 
@@ -133,6 +141,9 @@ export default class skyquakeContainer extends React.Component {
         return html;
     }
 }
+skyquakeContainer.childContextTypes = {
+  userProfile: React.PropTypes.object
+};
 skyquakeContainer.contextTypes = {
     router: React.PropTypes.object
   };
index 1403d0a..cb731c7 100644 (file)
@@ -179,7 +179,10 @@ class SkyquakeContainerStore {
                     self.closeSocket();
                 });
                 if (!_.isEqual(data.project, self.projects)) {
+                    let user = self.user;
+                    user.projects = data.project;
                     self.setState({
+                        user: user,
                         projects: data.project
                     });
                 }
@@ -209,7 +212,7 @@ class SkyquakeContainerStore {
 
         } else {
             if(!data) data = {};
-            state.notificationMessage = data.msg || 'Something very bad happened wrong';
+            state.notificationMessage = data.msg || 'Something wrong occurred. Check the network tab and console logs for more information.';
             if(data.type) {
                 state.notificationType = data.type;
             }
index a61ee54..51a21a0 100644 (file)
@@ -63,21 +63,28 @@ class SelectProject extends React.Component {
     render() {
         let props = this.props;
         let currentValue = JSON.stringify(props.currentProject);
-        let projects = this.props.projects.map((p,i) => {
+        let projects = this.props.projects && this.props.projects.map((p,i) => {
             return {
                 label: p.name,
                 value: p.name
             }
         });
+        let hasProjects = (this.props.projects && (this.props.projects.length > 0))
         return (
             <div className="userSection app">
-                Project:
-                <SelectOption
-                    options={projects}
-                    value={currentValue}
-                    defaultValue={currentValue}
-                    onChange={props.onSelectProject}
-                    className="projectSelect" />
+                {
+                    hasProjects ?  'Project:' : 'No Projects Assigned'
+                }
+                {
+                    hasProjects ?
+                    <SelectOption
+                        options={projects}
+                        value={currentValue}
+                        defaultValue={currentValue}
+                        onChange={props.onSelectProject}
+                        className="projectSelect" />
+                    : null
+                }
             </div>
         )
     }
@@ -96,12 +103,16 @@ class UserNav extends React.Component {
     }
     render() {
         let props = this.props;
+        let userProfileLink = '';
+        this.props.nav['user_management'] && this.props.nav['user_management'].routes.map((r) => {
+            if(r.unique) {
+                userProfileLink = returnLinkItem(r, props.currentUser)
+            }
+        })
         return (
             <div className="app">
                 <h2>
-                    <a>
-                        {props.currentUser}
-                    </a>
+                    {userProfileLink}
                     <span className="oi" data-glyph="caret-bottom"></span>
                 </h2>
                 <ul className="menu">
@@ -205,12 +216,12 @@ export function buildNavListItem (k, link, index) {
  * @param  {object} link Routing information from nav object.
  * @return {object}  component   returns a react component that links to a new route.
  */
-export function returnLinkItem(link) {
+export function returnLinkItem(link, label) {
     let ref;
     let route = link.route;
     if(link.isExternal) {
         ref = (
-            <a href={route}>{link.label}</a>
+            <a href={route}>{label || link.label}</a>
         )
     } else {
         if(link.path && link.path.replace(' ', '') != '') {
@@ -225,8 +236,8 @@ export function returnLinkItem(link) {
             }
         }
         ref = (
-           <Link to={route}>
-                {link.label}
+            <Link to={route}>
+                {label || link.label}
             </Link>
         )
     }
@@ -255,7 +266,8 @@ export function buildNav(nav, currentPlugin, props) {
                 projects={props.projects}
                 currentProject={props.currentProject} />
             <UserNav
-                currentUser={props.currentUser}  />
+                currentUser={props.currentUser}
+                nav={nav}  />
         </div>
     )
     for (let k in nav) {
index f15af7b..5269be9 100644 (file)
@@ -77,6 +77,7 @@
             padding:0.5rem 1rem;
             text-decoration:none;
             text-transform:uppercase;
+            text-align:center;
             color:white;
         }
         &:before {
@@ -93,6 +94,7 @@
             align-items:center;
             padding-left: 1rem;
             text-transform:uppercase;
+            text-align: center;
             border-left:1px solid white;
             .projectSelect {
                 padding: 0 0.5rem;
index 0d76a29..82b62a8 100644 (file)
     .userTable {
         .FormSection-body {
             max-width: 652px;
-            overflow-x: scroll;
+            overflow-x: auto;
         }
     }
 }
index 17a5a59..64761dc 100644 (file)
@@ -16,7 +16,6 @@ export default class ProjectManagementStore {
         this.selectedUser = null;
         this.selectedRole = null;
         this.roles = ['rw-project:project-admin', 'rw-project:project-oper', 'rw-project:project-create'
-        // ,'some_other_role', 'yet_another_role', 'operator_role', 'some_other_role', 'yet_another_role'
         ];
         this.users = [];
         this.activeIndex = null;
index 0e66f6f..0fe9e96 100644 (file)
         "route": "platform",
         "component": "./platformRoleManagement/platformRoleManagement.jsx",
         "type": "internal"
-    }]
+    },
+      {
+        "label": "User Profile",
+        "route": "user-profile",
+        "component": "./userProfile/userProfile.jsx",
+        "type": "internal",
+        "unique" : true
+    }
+    ]
 }
index f946cbb..28471b7 100644 (file)
@@ -301,7 +301,8 @@ class UserManagementDashboard extends React.Component {
 }
 // onClick={this.Store.update.bind(null, Account)}
 UserManagementDashboard.contextTypes = {
-    router: React.PropTypes.object
+    router: React.PropTypes.object,
+    userProfile: React.PropTypes.object
 };
 
 UserManagementDashboard.defaultProps = {
index d00be88..79511b0 100644 (file)
         padding: 0.5rem 0;
         border-top: #d3d3d3 1px solid;
     }
-}
-
-
-
-.FormSection {
-    &-title {
-        color: #000;
-        background: lightgray;
-        padding: 0.5rem;
-        border-top: 1px solid #f1f1f1;
-        border-bottom: 1px solid #f1f1f1;
-    }
-    &-body {
-        padding: 0.5rem 0.75rem;
-    }
-    label {
-        -ms-flex: 1 0;
-            flex: 1 0;
-    }
-    /* label {*/
-    /*     display: -ms-flexbox;*/
-    /*     display: flex;*/
-    /*     -ms-flex-direction: column;*/
-    /*     flex-direction: column;*/
-    /*     width: 100%;*/
-    /*     margin: 0.5rem 0;*/
-    /*     -ms-flex-align: start;*/
-    /*     align-items: flex-start;*/
-    /*     -ms-flex-pack: start;*/
-    /*     justify-content: flex-start;*/
-    /* }*/
-    select {
-        font-size: 1rem;
-        min-width: 75%;
-        height: 35px;
+    table {
+        font-size: 0.8rem;
+        thead {
+            border-bottom:1px solid #d3d3d3;
+            td{
+                font-weight:bold;
+            }
+        }
+        td{
+            padding:0.25rem 0.5rem;
+            vertical-align: middle;
+            .checkbox {
+                -ms-flex-pack:center;
+                    justify-content:center;
+            }
+        }
     }
 }
 
 
-.InputCollection {
-    display:-ms-flexbox;
-    display:flex;
-    -ms-flex-wrap: nowrap;
-        flex-wrap: nowrap;
-    -ms-flex-align: center;
-        align-items: center;
-    button {
-        padding: 0.25rem;
-        height: 1.5rem;
-        font-size: 0.75rem;
-    }
-    select {
-        min-width: 100%;
-    }
-    margin-bottom:0.5rem;
-    &-wrapper {
 
-    }
-}
+
 .tableRow {
     display:-ms-flexbox;
     display:flex;
diff --git a/skyquake/plugins/user_management/src/userProfile/userProfile.jsx b/skyquake/plugins/user_management/src/userProfile/userProfile.jsx
new file mode 100644 (file)
index 0000000..67d5515
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import UserProfileStore from './userProfileStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import 'style/layout.scss';
+import '../dashboard/userMgmt.scss';
+import {Panel, PanelWrapper} from 'widgets/panel/panel';
+
+
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import Input from 'widgets/form_controls/input.jsx';
+import Button, {ButtonGroup} from 'widgets/button/sq-button.jsx';
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import 'widgets/form_controls/formControls.scss';
+import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../node_modules/open-iconic/svg/trash.svg';
+
+class UserProfileDashboard extends React.Component {
+    constructor(props) {
+        super(props);
+        this.Store = this.props.flux.stores.hasOwnProperty('UserProfileStore') ? this.props.flux.stores.UserProfileStore : this.props.flux.createStore(UserProfileStore);
+       this.state = this.Store.getState();
+       this.actions = this.state.actions;
+
+    }
+    componentDidUpdate() {
+        let self = this;
+        ReactDOM.findDOMNode(this.UserList).addEventListener('transitionend', this.onTransitionEnd, false);
+        setTimeout(function() {
+            let element = self[`user-ref-${self.state.activeIndex}`]
+            element && !isElementInView(element) && element.scrollIntoView({block: 'end', behavior: 'smooth'});
+        })
+    }
+    componentWillMount() {
+        this.Store.listen(this.updateState);
+        this.Store.getUsers();
+    }
+    componentWillUnmount() {
+        this.Store.unlisten(this.updateState);
+    }
+    updateState = (state) => {
+        this.setState(state);
+    }
+    updateInput = (key, e) => {
+        let property = key;
+        this.actions.handleUpdateInput({
+            [property]:e.target.value
+        })
+    }
+    disabledChange = (e) => {
+        this.actions.handleDisabledChange(e.target.checked);
+    }
+    platformChange = (platformRole, e) => {
+        this.actions.handlePlatformRoleUpdate(platformRole, e.currentTarget.checked);
+    }
+    addProjectRole = (e) => {
+        this.actions.handleAddProjectItem();
+    }
+    removeProjectRole = (i, e) => {
+        this.actions.handleRemoveProjectItem(i);
+    }
+    updateProjectRole = (i, e) => {
+        this.actions.handleUpdateProjectRole(i, e)
+    }
+    addUser = () => {
+        this.actions.handleAddUser();
+    }
+    viewUser = (un, index) => {
+        this.actions.viewUser(un, index);
+    }
+    editUser = () => {
+        this.actions.editUser(false);
+    }
+    cancelEditUser = () => {
+        this.actions.editUser(true)
+    }
+    osePanel = () => {
+        this.actions.handleCloseUserPanel();
+    }
+    // updateUser = (e) => {
+    //     e.preventDefault();
+    //     e.stopPropagation();
+
+    //     this.Store.updateUser();
+    // }
+    deleteUser = (e) => {
+        e.preventDefault();
+        e.stopPropagation();
+        this.Store.deleteUser({
+                'user-name': this.state['user-name'],
+                'user-domain': this.state['user-domain']
+            });
+    }
+    createUser = (e) => {
+        e.preventDefault();
+        e.stopPropagation();
+        if(this.state['new-password'] != this.state['confirm-password']) {
+            this.props.actions.showNotification('Passwords do not match')
+        } else {
+            this.Store.createUser({
+                'user-name': this.state['user-name'],
+                'user-domain': this.state['user-domain'],
+                'password': this.state['new-password']
+                // 'confirm-password': this.state['confirm-password']
+            });
+        }
+    }
+    updateUser = (e) => {
+        let self = this;
+        e.preventDefault();
+        e.stopPropagation();
+        let validatedPasswords = validatePasswordFields(this.state);
+        if(validatedPasswords) {
+            this.Store.updateUser(_.merge({
+                            'user-name': this.context.userProfile.userId,
+                            'user-domain': this.state['user-domain'],
+                            'password': this.state['new-password']
+                        }));
+        }
+        function validatePasswordFields(state) {
+            let oldOne = state['old-password'];
+            let newOne = state['new-password'];
+            let confirmOne = state['confirm-password'];
+            if(true) {
+                if(oldOne == newOne) {
+                    self.props.actions.showNotification('Your new password must not match your old one');
+                    return false;
+                }
+                if(newOne != confirmOne) {
+                    self.props.actions.showNotification('Passwords do not match');
+                    return false;
+                }
+                return {
+                    // 'old-password': oldOne,
+                    'new-password': newOne,
+                    'confirm-password': confirmOne
+                }
+            } else {
+                return {};
+            }
+        }
+    }
+     evaluateSubmit = (e) => {
+        if (e.keyCode == 13) {
+            if (this.props.isEdit) {
+                this.updateUser(e);
+            } else {
+                this.createUser(e);
+            }
+            e.preventDefault();
+            e.stopPropagation();
+        }
+    }
+    onTransitionEnd = (e) => {
+        this.actions.handleHideColumns(e);
+        console.log('transition end')
+    }
+    disableChange = (e) => {
+        let value = e.target.value;
+        value = value.toUpperCase();
+        if (value=="TRUE") {
+            value = true;
+        } else {
+            value = false;
+        }
+        console.log(value)
+    }
+    render() {
+
+        let self = this;
+        let html;
+        let props = this.props;
+        let state = this.state;
+        let passwordSectionHTML = null;
+        let formButtonsHTML = (
+            <ButtonGroup className="buttonGroup">
+                <Button label="EDIT" type="submit" onClick={this.editUser} />
+            </ButtonGroup>
+        );
+        const User = this.context.userProfile || {};
+            passwordSectionHTML = (
+                                        (
+                                            <FormSection title="PASSWORD CHANGE">
+                                                <Input label="NEW PASSWORD" type="password" value={state['new-password']}  onChange={this.updateInput.bind(null, 'new-password')}/>
+                                                <Input label="REPEAT NEW PASSWORD" type="password"  value={state['confirm-password']}  onChange={this.updateInput.bind(null, 'confirm-password')}/>
+                                            </FormSection>
+                                        )
+                                    );
+            formButtonsHTML = (
+
+
+                                    <ButtonGroup className="buttonGroup">
+                                        <Button label="Update" type="submit" onClick={this.updateUser} />
+                                    </ButtonGroup>
+
+
+                            )
+
+        html = (
+            <PanelWrapper column>
+                <PanelWrapper className={`row userManagement ${!this.state.userOpen ? 'userList-open' : ''}`} style={{'flexDirection': 'row'}} >
+                    <PanelWrapper ref={(div) => { this.UserList = div}} className={`column userList expanded hideColumns`}>
+                        <Panel title={User.userId} style={{marginBottom: 0}} no-corners>
+                            <table>
+                                <thead>
+                                    <tr>
+                                        <td>User Name</td>
+                                        {
+                                            state.roles.map((r,i) => {
+                                                return <td key={i}>{r}</td>
+                                            })
+                                        }
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                    {
+                                        User.projects && User.projects.map((p,i)=> {
+                                            let projectConfig = p['project-config'];
+                                            let userRoles = [];
+                                            if(projectConfig && projectConfig.user) {
+                                                projectConfig.user.map((u) => {
+                                                    if(u['user-name'] == User.userId) {
+                                                        userRoles = u.role && u.role.map((r) => {
+                                                            return r.role;
+                                                        })
+                                                    }
+                                                })
+                                            }
+                                            return (
+                                                <tr key={i}>
+                                                    <td>
+                                                        {p.name}
+                                                    </td>
+                                                    {
+                                                        state.roles.map((r,j) => {
+                                                            return <td key={j}><Input readonly={state.isReadOnly} type="checkbox" checked={(userRoles.indexOf(r) > -1)} /></td>
+                                                        })
+                                                    }
+                                                </tr>
+                                            )
+                                        })
+                                    }
+                                </tbody>
+                            </table>
+                            {passwordSectionHTML}
+                        </Panel>
+                        {formButtonsHTML}
+                    </PanelWrapper>
+
+                </PanelWrapper>
+            </PanelWrapper>
+        );
+        return html;
+    }
+}
+// onClick={this.Store.update.bind(null, Account)}
+UserProfileDashboard.contextTypes = {
+    router: React.PropTypes.object,
+    userProfile: React.PropTypes.object
+};
+
+UserProfileDashboard.defaultProps = {
+    userList: [],
+    selectedUser: {}
+}
+
+export default SkyquakeComponent(UserProfileDashboard);
+
+
+function isElementInView(el) {
+    var rect = el && el.getBoundingClientRect() || {};
+
+    return (
+        rect.top >= 0 &&
+        rect.left >= 0 &&
+        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
+        rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
+    );
+}
+
+
+// isReadOnly={state.isReadOnly} disabled={state.disabled} onChange={this.disableChange}
+
+class isDisabled extends React.Component {
+    constructor(props) {
+        super(props);
+    }
+    render() {
+        let props = this.props;
+        return (<div/>)
+    }
+}
+
+/**
+ * AddItemFn:
+ */
+class InputCollection extends React.Component {
+    constructor(props) {
+        super(props);
+        this.collection = props.collection;
+    }
+    buildTextInput(onChange, v, i) {
+        return (
+            <Input
+                readonly={this.props.readonly}
+                style={{flex: '1 1'}}
+                key={i}
+                value={v}
+                onChange= {onChange.bind(null, i)}
+            />
+        )
+    }
+    buildSelectOption(initial, options, onChange, v, i) {
+        return (
+            <SelectOption
+                readonly={this.props.readonly}
+                key={`${i}-${v.replace(' ', '_')}`}
+                intial={initial}
+                defaultValue={v}
+                options={options}
+                onChange={onChange.bind(null, i)}
+            />
+        );
+    }
+    showInput() {
+
+    }
+    render() {
+        const props = this.props;
+        let inputType;
+        let className = "InputCollection";
+        if (props.className) {
+            className = `${className} ${props.className}`;
+        }
+        if (props.type == 'select') {
+            inputType = this.buildSelectOption.bind(this, props.initial, props.options, props.onChange);
+        } else {
+            inputType = this.buildTextInput.bind(this, props.onChange)
+        }
+        let html = (
+            <div className="InputCollection-wrapper">
+                {props.collection.map((v,i) => {
+                    return (
+                        <div key={i} className={className} >
+                            {inputType(v, i)}
+                            {
+                                props.readonly ? null : <span onClick={props.RemoveItemFn.bind(null, i)} className="removeInput"><img src={imgRemove} />Remove</span>}
+                        </div>
+                    )
+                })}
+                { props.readonly ? null : <span onClick={props.AddItemFn} className="addInput"><img src={imgAdd} />Add</span>}
+            </div>
+        );
+        return html;
+    }
+}
+
+InputCollection.defaultProps = {
+    input: Input,
+    collection: [],
+    onChange: function(i, e) {
+        console.log(`
+                        Updating with: ${e.target.value}
+                        At index of: ${i}
+                    `)
+    },
+    AddItemFn: function(e) {
+        console.log(`Adding a new item to collection`)
+    },
+    RemoveItemFn: function(i, e) {
+        console.log(`Removing item from collection at index of: ${i}`)
+    }
+}
+
+class FormSection extends React.Component {
+    render() {
+        let className = 'FormSection ' + this.props.className;
+        let html = (
+            <div
+                style={this.props.style}
+                className={className}
+            >
+                <div className="FormSection-title">
+                    {this.props.title}
+                </div>
+                <div className="FormSection-body">
+                    {this.props.children}
+                </div>
+            </div>
+        );
+        return html;
+    }
+}
+
+FormSection.defaultProps = {
+    className: ''
+}
diff --git a/skyquake/plugins/user_management/src/userProfile/userProfileActions.js b/skyquake/plugins/user_management/src/userProfile/userProfileActions.js
new file mode 100644 (file)
index 0000000..a0a34d9
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+module.exports = function(Alt) {
+   return Alt.generateActions(
+                                       'handleUpdateInput',
+                                       'handleAddProjectItem',
+                                       'handleRemoveProjectItem',
+                                       'handleUpdateProjectRole',
+                                       'viewUser',
+                                       'editUser',
+                                       'handleCloseUserPanel',
+                                       'handleHideColumns',
+                                       'getUsersSuccess',
+                                       'getUsersNotification',
+                                       'handleDisabledChange',
+                                       'handlePlatformRoleUpdate',
+                                       'handleAddUser',
+                                       'handleCreateUser',
+                                       'handleUpdateUser',
+                                       'updateUserSuccess',
+                                       'createUserSuccess',
+                                       'deleteUserSuccess'
+                                       );
+}
diff --git a/skyquake/plugins/user_management/src/userProfile/userProfileSource.js b/skyquake/plugins/user_management/src/userProfile/userProfileSource.js
new file mode 100644 (file)
index 0000000..267387a
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import $ from 'jquery';
+var Utils = require('utils/utils.js');
+let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
+let HOST = API_SERVER;
+let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
+let DEV_MODE = require('utils/rw.js').getSearchParams(window.location).dev_mode || false;
+
+if (DEV_MODE) {
+    HOST = window.location.protocol + '//' + window.location.hostname;
+}
+
+
+
+
+module.exports = function(Alt) {
+    return {
+        getUsers: {
+          remote: function() {
+              return new Promise(function(resolve, reject) {
+                $.ajax({
+                  url: `/user?api_server=${API_SERVER}`,
+                  type: 'GET',
+                  beforeSend: Utils.addAuthorizationStub,
+                  success: function(data, textStatus, jqXHR) {
+                    resolve(data.user);
+                  }
+                }).fail(function(xhr){
+                  //Authentication and the handling of fail states should be wrapped up into a connection class.
+                  Utils.checkAuthentication(xhr.status);
+                  let msg = xhr.responseText;
+                  if(xhr.errorMessage) {
+                    msg = xhr.errorMessage
+                  }
+                  reject(msg);
+                });
+              });
+          },
+          interceptResponse: interceptResponse({
+            'error': 'There was an error retrieving the resource orchestrator information.'
+          }),
+          success: Alt.actions.global.getUsersSuccess,
+          loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        updateUser: {
+          remote: function(state, user) {
+            return new Promise(function(resolve, reject) {
+              $.ajax({
+                  url: `/user?api_server=${API_SERVER}`,
+                  type: 'PUT',
+                  data: user,
+                  beforeSend: Utils.addAuthorizationStub,
+                  success: function(data, textStatus, jqXHR) {
+                    resolve(data);
+                  }
+                }).fail(function(xhr){
+                  //Authentication and the handling of fail states should be wrapped up into a connection class.
+                  Utils.checkAuthentication(xhr.status);
+                  let msg = xhr.responseText;
+                  if(xhr.errorMessage) {
+                    msg = xhr.errorMessage
+                  }
+                  reject(msg);
+                });
+            });
+          },
+          interceptResponse: interceptResponse({
+            'error': 'There was an error updating the user.'
+          }),
+          success: Alt.actions.global.updateUserSuccess,
+          loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        deleteUser: {
+          remote: function(state, user) {
+            return new Promise(function(resolve, reject) {
+              $.ajax({
+                url: `/user/${user['user-name']}/${user['user-domain']}?api_server=${API_SERVER}`,
+                type: 'DELETE',
+                data: user,
+                beforeSend: Utils.addAuthorizationStub,
+                success: function(data, textStatus, jqXHR) {
+                  resolve(data);
+                }
+              }).fail(function(xhr){
+                //Authentication and the handling of fail states should be wrapped up into a connection class.
+                Utils.checkAuthentication(xhr.status);
+                let msg = xhr.responseText;
+                if(xhr.errorMessage) {
+                  msg = xhr.errorMessage
+                }
+                reject(msg);
+              });
+            });
+          },
+          interceptResponse: interceptResponse({
+            'error': 'There was an error deleting the user.'
+          }),
+          success: Alt.actions.global.deleteUserSuccess,
+          loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        createUser: {
+            remote: function(state, user) {
+
+              return new Promise(function(resolve, reject) {
+                $.ajax({
+                  url: `/user?api_server=${API_SERVER}`,
+                  type: 'POST',
+                  data: user,
+                  beforeSend: Utils.addAuthorizationStub,
+                  success: function(data, textStatus, jqXHR) {
+                    resolve(data);
+                  }
+                }).fail(function(xhr){
+                  //Authentication and the handling of fail states should be wrapped up into a connection class.
+                  Utils.checkAuthentication(xhr.status);
+                  let msg = xhr.responseText;
+                  if(xhr.errorMessage) {
+                    msg = xhr.errorMessage
+                  }
+                  reject(msg);
+                });
+              });
+            },
+            interceptResponse: interceptResponse({
+              'error': 'There was an error updating the account.'
+            }),
+            success: Alt.actions.global.createUserSuccess,
+            loading: Alt.actions.global.showScreenLoader,
+            error: Alt.actions.global.showNotification
+        }
+      }
+}
+
+function interceptResponse (responses) {
+  return function(data, action, args) {
+    if(responses.hasOwnProperty(data)) {
+      return {
+        type: data,
+        msg: responses[data]
+      }
+    } else {
+      return data;
+    }
+  }
+}
+
diff --git a/skyquake/plugins/user_management/src/userProfile/userProfileStore.js b/skyquake/plugins/user_management/src/userProfile/userProfileStore.js
new file mode 100644 (file)
index 0000000..8febbd1
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import UserProfileActions from './userProfileActions.js';
+import UserProfileSource from './userProfileSource.js';
+import _ from 'lodash';
+export default class UserProfileStore {
+    constructor() {
+        this.actions = UserProfileActions(this.alt);
+        this.bindActions(this.actions);
+        this.registerAsync(UserProfileSource);
+        this.users = [];
+        this['user-name'] = '';
+        this['user-domain'] = 'system';
+        this.disabled = false;
+        this.roles = ['rw-project:project-admin', 'rw-project:project-oper', 'rw-project:project-create'
+        ];
+        this.currentPassword = '';
+        this['old-password'] = '';
+        this['new-password'] = '';
+        this['confirm-password'] = '';
+
+        this.activeIndex = null;
+        this.isReadOnly = true;
+        this.userOpen = false;
+        this.hideColumns = false;
+        this.isEdit = false;
+        // this.exportPublicMethods({})
+    }
+    /**
+     * [handleFieldUpdate description]
+     * @param  {Object} data {
+     *                       [store_property] : [value]
+     * }
+     * @return {[type]}      [description]
+     */
+    handleUpdateInput(data) {
+        this.setState(data);
+    }
+    handleAddProjectItem(item) {
+        let projectRoles = this.projectRoles;
+        projectRoles.push('');
+        this.setState({projectRoles});
+    }
+    handleRemoveProjectItem(i) {
+        let projectRoles = this.projectRoles;
+        projectRoles.splice(i, 1);
+        console.log('Removing', projectRoles)
+        this.setState({projectRoles});
+    }
+    handleUpdateProjectRole(data) {
+        let i = data[0];
+        let e = data[1];
+        let projectRoles = this.projectRoles
+        projectRoles[i] = JSON.parse(e.currentTarget.value);
+        this.setState({
+            projectRoles
+        });
+    }
+    viewUser(data) {
+        let user = data[0];
+        let userIndex = data[1];
+
+        let ActiveUser = {
+            'user-name': user['user-name'],
+            'user-domain': user['user-domain'],
+            platformRoles: user.platformRoles || this.platformRoles,
+            disabled: user.disabled || this.disabled,
+            projectRoles: user.projectRoles || this.projectRoles
+        }
+        let state = _.merge({
+            activeIndex: userIndex,
+            userOpen: true,
+            isEdit: true,
+            isReadOnly: true
+        }, ActiveUser);
+        this.setState(state)
+    }
+    editUser(isEdit) {
+        this.setState({
+            isReadOnly: isEdit
+        })
+    }
+    handleCloseUserPanel() {
+        this.setState({
+            userOpen: false,
+            isEdit: false,
+            isReadOnly: true
+        })
+    }
+    handleHideColumns(e) {
+        if(this.userOpen && e.currentTarget.classList.contains('hideColumns')) {
+            this.setState({
+                hideColumns: true
+            })
+        } else {
+            this.setState({
+                hideColumns: false
+            })
+        }
+    }
+    handleDisabledChange(isDisabled){
+        this.setState({
+            disabled: isDisabled
+        })
+    }
+    handlePlatformRoleUpdate(data){
+        let platform_role = data[0];
+        let checked = data[1];
+        let platformRoles = this.platformRoles;
+        platformRoles[platform_role] = checked;
+        this.setState({
+            platformRoles
+        })
+    }
+    resetUser() {
+        let username = '';
+        let domain = 'system';
+        let disabled = false;
+        let platformRoles = {
+            super_admin: false,
+            platform_admin: false,
+            platform_oper: false
+        };
+        let projectRoles = [];
+        let currentPassword = '';
+        let oldPassword = '';
+        let newPassword = '';
+        let confirmPassword = '';
+        return {
+            'user-name' : username,
+            'user-domain' : domain,
+            disabled,
+            platformRoles,
+            projectRoles,
+            currentPassword,
+            'old-password': oldPassword,
+            'new-password': newPassword,
+            'confirm-password': confirmPassword
+        }
+    }
+    resetPassword() {
+        let currentPassword = '';
+        let oldPassword = '';
+        let newPassword = '';
+        let confirmPassword = '';
+        return {
+            currentPassword,
+            'old-password': oldPassword,
+            'new-password': newPassword,
+            'confirm-password': confirmPassword
+        }
+    }
+    handleAddUser() {
+        this.setState(_.merge( this.resetUser() ,
+                              {
+                                isEdit: false,
+                                userOpen: true,
+                                activeIndex: null,
+                                isReadOnly: false
+                            }
+        ))
+    }
+    handleCreateUser() {
+
+    }
+    handleUpdateUser() {
+
+    }
+
+    getUsersSuccess(users) {
+        this.alt.actions.global.hideScreenLoader.defer();
+        this.setState({users});
+    }
+    updateUserSuccess() {
+        this.alt.actions.global.hideScreenLoader.defer();
+        let users = this.users || [];
+        users[this.activeIndex] = {
+            'user-name': this['user-name'],
+            'user-domain': this['user-domain'],
+            platformRoles: this.platformRoles,
+            disabled: this.disabled,
+            projectRoles: this.projectRoles
+        }
+        this.setState({
+            users,
+            isEdit: true,
+            isReadOnly: true
+        })
+    }
+    deleteUserSuccess() {
+        this.alt.actions.global.hideScreenLoader.defer();
+        let users = this.users;
+        users.splice(this.activeIndex, 1);
+        this.setState({users, userOpen: false})
+    }
+    createUserSuccess() {
+        this.alt.actions.global.hideScreenLoader.defer();
+        let users = this.users || [];
+        users.push({
+            'user-name': this['user-name'],
+            'user-domain': this['user-domain'],
+            platformRoles: this.platformRoles,
+            disabled: this.disabled,
+            projectRoles: this.projectRoles,
+         });
+        let newState = {
+            users,
+            isEdit: true,
+            isReadOnly: true,
+            activeIndex: users.length - 1
+        };
+        _.merge(newState, this.resetPassword())
+        this.setState(newState);
+    }
+}