Project UI updates and bug fixes
authorLaurence Maultsby <laurence.maultsby@riftio.com>
Tue, 28 Mar 2017 20:12:23 +0000 (16:12 -0400)
committerLaurence Maultsby <laurence.maultsby@riftio.com>
Tue, 28 Mar 2017 20:12:30 +0000 (16:12 -0400)
Signed-off-by: Laurence Maultsby <laurence.maultsby@riftio.com>
19 files changed:
skyquake/framework/core/modules/api/sessions.js
skyquake/framework/core/modules/api/userManagementAPI.js
skyquake/framework/core/modules/routes/userManagement.js
skyquake/framework/widgets/form_controls/selectOption.jsx
skyquake/framework/widgets/skyquake_container/skyquakeApp.scss
skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx
skyquake/framework/widgets/skyquake_container/skyquakeContainerActions.js
skyquake/framework/widgets/skyquake_container/skyquakeContainerSource.js
skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js
skyquake/framework/widgets/skyquake_container/skyquakeNav.jsx [deleted file]
skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx [new file with mode: 0644]
skyquake/framework/widgets/skyquake_nav/skyquakeNav.scss [new file with mode: 0644]
skyquake/plugins/project_management/src/dashboard/dashboard.jsx
skyquake/plugins/project_management/src/dashboard/projectMgmtSource.js
skyquake/plugins/project_management/src/dashboard/projectMgmtStore.js
skyquake/plugins/user_management/src/dashboard/dashboard.jsx
skyquake/plugins/user_management/src/dashboard/userMgmtSource.js
skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementSource.js
skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementStore.js

index b609503..0ce0300 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -109,7 +109,10 @@ sessionsAPI.create = function(req, res) {
 
             req.session.authorization = authorization_header_string;
             req.session.loggedIn = true;
-
+            req.session.userdata = {
+                username: username,
+                // project: req.session.projectId
+            };
             var successMsg = 'User =>' + username + ' successfully logged in.';
             successMsg += req.session.projectId ? 'Project =>' + req.session.projectId + ' set as default.' : '';
 
index e88a5dc..19bb854 100644 (file)
@@ -33,7 +33,7 @@ UserManagement.get = function(req) {
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + '/api/operational/user-config/users',
+                uri: utils.confdPort(api_server) + '/api/operational/user-config/user',
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
                     'Authorization': req.session && req.session.authorization
@@ -46,7 +46,7 @@ UserManagement.get = function(req) {
             var response = {};
             response['data'] = {};
             if (result[0].body) {
-                response['data']['users'] = JSON.parse(result[0].body)['rw-user:users'];
+                response['data']['user'] = JSON.parse(result[0].body)['rw-user:user'];
             }
             response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
 
@@ -62,12 +62,30 @@ UserManagement.get = function(req) {
         });
     });
 };
+
+UserManagement.getProfile = function(req) {
+    var self = this;
+    var api_server = req.query['api_server'];
+    return new Promise(function(resolve, reject) {
+        var response = {};
+            response['data'] = {
+            userId: req.session.userdata.username,
+            projectId: req.session.projectId
+        };
+            // if (result[0].body) {
+            //     response['data']['users'] = JSON.parse(result[0].body)['rw-user:users'];
+            // }
+        response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+        resolve(response);
+    });
+};
 UserManagement.create = function(req) {
     var self = this;
     var api_server = req.query['api_server'];
     var data = req.body;
     data = {
-        "users":[data]
+        "user":[data]
     }
     return new Promise(function(resolve, reject) {
         Promise.all([
@@ -107,7 +125,7 @@ UserManagement.update = function(req) {
     var api_server = req.query['api_server'];
     var bodyData = req.body;
     data = {
-        "users":[bodyData]
+        "user":[bodyData]
     }
     var updateTasks = [];
     if(bodyData.hasOwnProperty('old-password')) {
@@ -174,7 +192,7 @@ UserManagement.delete = function(req) {
     var domain = req.params.domain;
     var api_server = req.query["api_server"];
     var requestHeaders = {};
-    var url = `${utils.confdPort(api_server)}/api/config/user-config/users/${username},${domain}`
+    var url = `${utils.confdPort(api_server)}/api/config/user-config/user/${username},${domain}`
     return new Promise(function(resolve, reject) {
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data,
index 11fb8b1..4e272d0 100644 (file)
@@ -43,6 +43,13 @@ Router.get('/user', cors(), function(req, res) {
         utils.sendErrorResponse(error, res);
     });
 });
+Router.get('/user-profile', cors(), function(req, res) {
+    UserManagementAPI.getProfile(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
 Router.post('/user', cors(), function(req, res) {
     UserManagementAPI.create(req).then(function(response) {
         utils.sendSuccessResponse(response, res);
index 1e02f15..397963e 100644 (file)
@@ -28,7 +28,7 @@ export default class SelectOption extends React.Component {
   render() {
     let html;
     let defaultValue = this.props.defaultValue;
-    let options =  this.props.options.map(function(op, i) {
+    let options =  this.props.options && this.props.options.map(function(op, i) {
     let value;
     let label;
     if(typeof(op) == 'object') {
@@ -40,12 +40,12 @@ export default class SelectOption extends React.Component {
     }
 
       return <option key={i} value={JSON.stringify(value)}>{label}</option>
-    });
+    }) || [];
     if (this.props.initial) {
       options.unshift(<option key='blank' value={JSON.stringify(this.props.defaultValue)}></option>);
     }
     html = (
-        <label key={this.props.key}>
+        <label key={this.props.key} className={this.props.className}>
             {this.props.label}
             {
               this.props.readonly ? defaultValue
index 77e72cf..b2493a3 100644 (file)
@@ -16,95 +16,7 @@ html, body {
     h1 {
         text-transform: uppercase;
     }
-    .active {
-        background-color: $brand-blue!important;
-        border-color: $brand-blue!important;
-        color: #fff!important
-    }
-    .skyquakeNav {
-        display: -ms-flexbox;
-        display: flex;
-        color:white;
-        background:black;
-        position:relative;
-        z-index: 10;
-        font-size:0.75rem;
-        .secondaryNav {
-            -ms-flex: 1 1 auto;
-                flex: 1 1 auto;
-            display: -ms-flexbox;
-            display: flex;
-            -ms-flex-pack: end;
-                justify-content: flex-end;
-        }
-        .app {
-            position:relative;
-            h2 {
-                font-size:0.75rem;
-                border-right: 1px solid black;
-                display: -ms-flexbox;
-                display: flex;
-                -ms-flex-align: center;
-                    align-items: center;
-                .oi {
-                    padding-right: 0.5rem;
-                }
-            }
-            .menu {
-                position:absolute;
-                display:none;
-                z-index:2;
-                width: 100%;
-            }
-            &:first-child{
-                h2 {
-                    border-left: 1px solid black;
-                }
-            }
-            &:hover {
-                a {
-                    color:$brand-blue-light;
-                    cursor:pointer;
-                }
-                .menu {
-                    display:block;
-                    background:black;
-                    a {
-                        color:white;
-                    }
-                    li:hover {
-                        a {
-                            color:$brand-blue-light;
-                        }
-                    }
-                }
-            }
-            &.active {
-                color:white;
-                background:black;
-                a {
-                    color:white;
-                }
-            }
-        }
-        a{
-            display:block;
-            padding:0.5rem 1rem;
-            text-decoration:none;
-            text-transform:uppercase;
-            color:white;
-        }
-        &:before {
-            content: '';
-            height:1.85rem;
-            width:5.5rem;
-            /*margin:0 1rem;*/
-            /*padding:0 0.125rem;*/
-            /*background: url('../../style/img/svg/riftio_logo_white.svg') no-repeat center center;*/
-            background: url('../../style/img/svg/osm-logo_color_rgb_white_text.svg') no-repeat center center;
-            background-size: contain;
-        }
-    }
+
     .skyquakeContainerWrapper {
     }
     .titleBar {
index 558cc62..b8d768c 100644 (file)
@@ -18,7 +18,7 @@
 import React from 'react';
 import AltContainer from 'alt-container';
 import Alt from './skyquakeAltInstance.js';
-import SkyquakeNav from './skyquakeNav.jsx';
+import SkyquakeNav from '../skyquake_nav/skyquakeNav.jsx';
 import EventCenter from './eventCenter.jsx';
 import SkyquakeContainerActions from './skyquakeContainerActions.js'
 import SkyquakeContainerStore from './skyquakeContainerStore.js';
@@ -111,7 +111,8 @@ export default class skyquakeContainer extends React.Component {
                         <ScreenLoader show={displayScreenLoader}/>
                         <SkyquakeNav nav={this.state.nav}
                             currentPlugin={this.state.currentPlugin}
-                            store={SkyquakeContainerStore} />
+                            store={SkyquakeContainerStore}
+                            projects={this.state.projects} />
                         <div className="titleBar">
                             <h1>{(this.state.nav.name ? this.state.nav.name.replace('_', ' ').replace('-', ' ') : this.state.currentPlugin && this.state.currentPlugin.replace('_', ' ').replace('-', ' ')) + tag}</h1>
                         </div>
index 773978b..c03ae6f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,5 +29,7 @@ export default Alt.generateActions(
     'hideNotification',
     //Screen Loader
     'showScreenLoader',
-    'hideScreenLoader'
+    'hideScreenLoader',
+    'openProjectSocketSuccess',
+    'getUserProfileSuccess'
 );
index ae2147a..7ea5ffc 100644 (file)
@@ -113,6 +113,56 @@ export default {
             success: SkyquakeContainerActions.openNotificationsSocketSuccess,
             error: SkyquakeContainerActions.openNotificationsSocketError
         }
+    },
+    openProjectSocket() {
+            return {
+                remote: function(state) {
+                    return new Promise(function(resolve, reject) {
+                        //If socket connection already exists, eat the request.
+                        if(state.socket) {
+                            return resolve(false);
+                        }
+                        $.ajax({
+                            url: '/socket-polling',
+                            type: 'POST',
+                            beforeSend: Utils.addAuthorizationStub,
+                            data: {
+                                url: '/project?api_server=' + API_SERVER
+                            },
+                            success: function(data, textStatus, jqXHR) {
+                                Utils.checkAndResolveSocketRequest(data, resolve, reject);
+                            }
+                            })
+                        .fail(function(xhr){
+                            //Authentication and the handling of fail states should be wrapped up into a connection class.
+                            Utils.checkAuthentication(xhr.status);
+                        });;
+                    });
+                },
+            success: SkyquakeContainerActions.openProjectSocketSuccess
+        }
+    },
+
+    getUserProfile() {
+        return {
+            remote: function(state, recordID) {
+                return new Promise(function(resolve, reject) {
+                    $.ajax({
+                        url: '/user-profile?api_server=' + API_SERVER,
+                        type: 'GET',
+                        beforeSend: Utils.addAuthorizationStub,
+                        success: function(data) {
+                            resolve(data);
+                        }
+                    }).fail(function(xhr) {
+                        //Authentication and the handling of fail states should be wrapped up into a connection class.
+                        Utils.checkAuthentication(xhr.status);
+                    });;
+                });
+            },
+            success: SkyquakeContainerActions.getUserProfileSuccess
+        }
     }
+
 }
 
index d1a8a9e..07010cd 100644 (file)
@@ -20,6 +20,7 @@
 import Alt from './skyquakeAltInstance.js';
 import SkyquakeContainerSource from './skyquakeContainerSource.js';
 import SkyquakeContainerActions from './skyquakeContainerActions';
+let Utils = require('utils/utils.js');
 import _ from 'lodash';
 //Temporary, until api server is on same port as webserver
 var rw = require('utils/rw.js');
@@ -32,6 +33,8 @@ class SkyquakeContainerStore {
         this.nav = {};
         this.notifications = [];
         this.socket = null;
+        this.projects = [];
+        this.user = {};
         //Notification defaults
         this.notificationMessage = '';
         this.displayNotification = false;
@@ -161,6 +164,32 @@ class SkyquakeContainerStore {
         })
     }
 
+    openProjectSocketSuccess = (connection) => {
+        var self = this;
+        var ws = window.multiplexer.channel(connection);
+        if (!connection) return;
+        self.setState({
+            socket: ws.ws,
+            channelId: connection
+        });
+        ws.onmessage = function(socket) {
+            try {
+                var data = JSON.parse(socket.data);
+                Utils.checkAuthentication(data.statusCode, function() {
+                    self.closeSocket();
+                });
+
+                self.setState({
+                    projects: data.project
+                });
+            } catch(e) {
+                console.log('HIT an exception in openProjectSocketSuccess', e);
+            }
+        };
+    }
+    getUserProfileSuccess = (user) => {
+        this.setState({user})
+    }
     //Notifications
     showNotification = (data) => {
         let state = {
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeNav.jsx b/skyquake/framework/widgets/skyquake_container/skyquakeNav.jsx
deleted file mode 100644 (file)
index 73a2f02..0000000
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 
- *   Copyright 2016 RIFT.IO Inc
- *
- *   Licensed under the Apache License, Version 2.0 (the "License");
- *   you may not use this file except in compliance with the License.
- *   You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing, software
- *   distributed under the License is distributed on an "AS IS" BASIS,
- *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *   See the License for the specific language governing permissions and
- *   limitations under the License.
- *
- */
-
-import React from 'react';
-import { Link } from 'react-router';
-import Utils from 'utils/utils.js';
-import Crouton from 'react-crouton';
-import 'style/common.scss';
-
-//Temporary, until api server is on same port as webserver
-var rw = require('utils/rw.js');
-var API_SERVER = rw.getSearchParams(window.location).api_server;
-var UPLOAD_SERVER = rw.getSearchParams(window.location).upload_server;
-
-//
-// Internal classes/functions
-//
-
-class LogoutAppMenuItem extends React.Component {
-    handleLogout() {
-        Utils.clearAuthentication();
-    }
-    render() {
-        return (
-            <div className="app">
-                <h2>
-                    <a onClick={this.handleLogout}>
-                        Logout
-                    </a>
-                </h2>
-            </div>
-        );
-    }
-}
-
-
-//
-// Exported classes and functions
-//
-
-//
-/**
- * Skyquake Nav Component. Provides navigation functionality between all plugins
- */
-export default class skyquakeNav extends React.Component {
-    constructor(props) {
-        super(props);
-        this.state = {};
-        this.state.validateErrorEvent = 0;
-        this.state.validateErrorMsg = '';
-    }
-    validateError = (msg) => {
-        this.setState({
-            validateErrorEvent: true,
-            validateErrorMsg: msg
-        });
-    }
-    validateReset = () => {
-        this.setState({
-            validateErrorEvent: false
-        });
-    }
-    returnCrouton = () => {
-        return <Crouton
-            id={Date.now()}
-            message={this.state.validateErrorMsg}
-            type={"error"}
-            hidden={!(this.state.validateErrorEvent && this.state.validateErrorMsg)}
-            onDismiss={this.validateReset}
-        />;
-    }
-    render() {
-        let html;
-        html = (
-                <div>
-                {this.returnCrouton()}
-            <nav className="skyquakeNav">
-                {buildNav.call(this, this.props.nav, this.props.currentPlugin)}
-            </nav>
-
-            </div>
-        )
-        return html;
-    }
-}
-skyquakeNav.defaultProps = {
-    nav: {}
-}
-/**
- * Returns a React Component
- * @param  {object} link  Information about the nav link
- * @param  {string} link.route Hash route that the SPA should resolve
- * @param  {string} link.name Link name to be displayed
- * @param  {number} index index of current array item
- * @return {object} component A React LI Component
- */
-//This should be extended to also make use of internal/external links and determine if the link should refer to an outside plugin or itself.
-export function buildNavListItem (k, link, index) {
-    let html = false;
-    if (link.type == 'external') {
-        this.hasSubNav[k] = true;
-        html = (
-            <li key={index}>
-                {returnLinkItem(link)}
-            </li>
-        );
-    }
-    return html;
-}
-
-/**
- * Builds a link to a React Router route or a new plugin route.
- * @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) {
-    let ref;
-    let route = link.route;
-    if(link.isExternal) {
-        ref = (
-            <a href={route}>{link.label}</a>
-        )
-    } else {
-        if(link.path && link.path.replace(' ', '') != '') {
-            route = link.path;
-        }
-        if(link.query) {
-            let query = {};
-            query[link.query] = '';
-            route = {
-                pathname: route,
-                query: query
-            }
-        }
-        ref = (
-           <Link to={route}>
-                {link.label}
-            </Link>
-        )
-    }
-    return ref;
-}
-
-/**
- * Constructs nav for each plugin, along with available subnavs
- * @param  {array} nav List returned from /nav endpoint.
- * @return {array}     List of constructed nav element for each plugin
- */
-export function buildNav(nav, currentPlugin) {
-    let navList = [];
-    let navListHTML = [];
-    let secondaryNav = [];
-    let self = this;
-    self.hasSubNav = {};
-    let secondaryNavHTML = (
-        <div className="secondaryNav" key="secondaryNav">
-        {secondaryNav}
-            <LogoutAppMenuItem />
-        </div>
-    )
-    for (let k in nav) {
-        if (nav.hasOwnProperty(k)) {
-            self.hasSubNav[k] = false;
-            let header = null;
-            let navClass = "app";
-            let routes = nav[k].routes;
-            let navItem = {};
-            //Primary plugin title and link to dashboard.
-            let route;
-            let NavList;
-            if (API_SERVER) {
-                route = routes[0].isExternal ? '/' + k + '/index.html?api_server=' + API_SERVER + '' + '&upload_server=' + UPLOAD_SERVER + '' : '';
-            } else {
-                route = routes[0].isExternal ? '/' + k + '/' : '';
-            }
-            let dashboardLink = returnLinkItem({
-                isExternal: routes[0].isExternal,
-                pluginName: nav[k].pluginName,
-                label: nav[k].label || k,
-                route: route
-            });
-            if (nav[k].pluginName == currentPlugin) {
-                navClass += " active";
-            }
-            NavList = nav[k].routes.map(buildNavListItem.bind(self, k));
-            navItem.priority = nav[k].priority;
-            navItem.order = nav[k].order;
-            navItem.html = (
-                <div key={k} className={navClass}>
-                    <h2>{dashboardLink} {self.hasSubNav[k] ? <span className="oi" data-glyph="caret-bottom"></span> : ''}</h2>
-                    <ul className="menu">
-                        {
-                            NavList
-                        }
-                    </ul>
-                </div>
-            );
-            navList.push(navItem)
-        }
-    }
-    //Sorts nav items by order and returns only the markup
-    navListHTML = navList.sort((a,b) => a.order - b.order).map(function(n) {
-        if((n.priority  < 2)){
-            return n.html;
-        } else {
-            secondaryNav.push(n.html);
-        }
-    });
-    navListHTML.push(secondaryNavHTML);
-    return navListHTML;
-}
diff --git a/skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx b/skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx
new file mode 100644 (file)
index 0000000..01158c7
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ *
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+import React from 'react';
+import { Link } from 'react-router';
+import Utils from 'utils/utils.js';
+import Crouton from 'react-crouton';
+import 'style/common.scss';
+
+import './skyquakeNav.scss';
+import SelectOption from '../form_controls/selectOption.jsx';
+import {FormSection} from '../form_controls/formControls.jsx';
+
+//Temporary, until api server is on same port as webserver
+var rw = require('utils/rw.js');
+var API_SERVER = rw.getSearchParams(window.location).api_server;
+var UPLOAD_SERVER = rw.getSearchParams(window.location).upload_server;
+
+//
+// Internal classes/functions
+//
+
+class LogoutAppMenuItem extends React.Component {
+    handleLogout() {
+        Utils.clearAuthentication();
+    }
+    render() {
+        return (
+            <div className="app">
+                <h2>
+                    <a onClick={this.handleLogout}>
+                        Logout
+                    </a>
+                </h2>
+            </div>
+        );
+    }
+}
+
+
+class UserNav extends React.Component {
+    constructor(props) {
+        super(props);
+    }
+    selectProject(e) {
+        let value = JSON.parse(e.currentTarget.value)
+        console.log('selected project', value)
+    }
+    render() {
+        let projects = this.props.projects.map((p,i) => {
+            return {
+                label: p.name,
+                value: p
+            }
+        })
+        return (
+            <div className="userSection">
+                Project:
+                <SelectOption options={projects} onChange={this.selectProject} className="projectSelect"/>
+            </div>
+        )
+    }
+}
+
+UserNav.defaultProps = {
+    projects: [
+
+    ]
+}
+
+//
+// Exported classes and functions
+//
+
+//
+/**
+ * Skyquake Nav Component. Provides navigation functionality between all plugins
+ */
+export default class skyquakeNav extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {};
+        this.state.validateErrorEvent = 0;
+        this.state.validateErrorMsg = '';
+    }
+    componentDidMount() {
+        this.props.store.openProjectSocket();
+        this.props.store.getUserProfile();
+    }
+    validateError = (msg) => {
+        this.setState({
+            validateErrorEvent: true,
+            validateErrorMsg: msg
+        });
+    }
+    validateReset = () => {
+        this.setState({
+            validateErrorEvent: false
+        });
+    }
+    returnCrouton = () => {
+        return <Crouton
+            id={Date.now()}
+            message={this.state.validateErrorMsg}
+            type={"error"}
+            hidden={!(this.state.validateErrorEvent && this.state.validateErrorMsg)}
+            onDismiss={this.validateReset}
+        />;
+    }
+    render() {
+        let html;
+        html = (
+                <div>
+                {this.returnCrouton()}
+            <nav className="skyquakeNav">
+                {buildNav.call(this, this.props.nav, this.props.currentPlugin, this.props)}
+            </nav>
+
+            </div>
+        )
+        return html;
+    }
+}
+skyquakeNav.defaultProps = {
+    nav: {}
+}
+/**
+ * Returns a React Component
+ * @param  {object} link  Information about the nav link
+ * @param  {string} link.route Hash route that the SPA should resolve
+ * @param  {string} link.name Link name to be displayed
+ * @param  {number} index index of current array item
+ * @return {object} component A React LI Component
+ */
+//This should be extended to also make use of internal/external links and determine if the link should refer to an outside plugin or itself.
+export function buildNavListItem (k, link, index) {
+    let html = false;
+    if (link.type == 'external') {
+        this.hasSubNav[k] = true;
+        html = (
+            <li key={index}>
+                {returnLinkItem(link)}
+            </li>
+        );
+    }
+    return html;
+}
+
+/**
+ * Builds a link to a React Router route or a new plugin route.
+ * @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) {
+    let ref;
+    let route = link.route;
+    if(link.isExternal) {
+        ref = (
+            <a href={route}>{link.label}</a>
+        )
+    } else {
+        if(link.path && link.path.replace(' ', '') != '') {
+            route = link.path;
+        }
+        if(link.query) {
+            let query = {};
+            query[link.query] = '';
+            route = {
+                pathname: route,
+                query: query
+            }
+        }
+        ref = (
+           <Link to={route}>
+                {link.label}
+            </Link>
+        )
+    }
+    return ref;
+}
+
+
+
+
+/**
+ * Constructs nav for each plugin, along with available subnavs
+ * @param  {array} nav List returned from /nav endpoint.
+ * @return {array}     List of constructed nav element for each plugin
+ */
+export function buildNav(nav, currentPlugin, props) {
+    let navList = [];
+    let navListHTML = [];
+    let secondaryNav = [];
+    let self = this;
+    self.hasSubNav = {};
+    let secondaryNavHTML = (
+        <div className="secondaryNav" key="secondaryNav">
+            {secondaryNav}
+            <UserNav projects={props.projects}/>
+            <LogoutAppMenuItem />
+        </div>
+    )
+    for (let k in nav) {
+        if (nav.hasOwnProperty(k)) {
+            self.hasSubNav[k] = false;
+            let header = null;
+            let navClass = "app";
+            let routes = nav[k].routes;
+            let navItem = {};
+            //Primary plugin title and link to dashboard.
+            let route;
+            let NavList;
+            if (API_SERVER) {
+                route = routes[0].isExternal ? '/' + k + '/index.html?api_server=' + API_SERVER + '' + '&upload_server=' + UPLOAD_SERVER + '' : '';
+            } else {
+                route = routes[0].isExternal ? '/' + k + '/' : '';
+            }
+            let dashboardLink = returnLinkItem({
+                isExternal: routes[0].isExternal,
+                pluginName: nav[k].pluginName,
+                label: nav[k].label || k,
+                route: route
+            });
+            if (nav[k].pluginName == currentPlugin) {
+                navClass += " active";
+            }
+            NavList = nav[k].routes.map(buildNavListItem.bind(self, k));
+            navItem.priority = nav[k].priority;
+            navItem.order = nav[k].order;
+            navItem.html = (
+                <div key={k} className={navClass}>
+                    <h2>{dashboardLink} {self.hasSubNav[k] ? <span className="oi" data-glyph="caret-bottom"></span> : ''}</h2>
+                    <ul className="menu">
+                        {
+                            NavList
+                        }
+                    </ul>
+                </div>
+            );
+            navList.push(navItem)
+        }
+    }
+    //Sorts nav items by order and returns only the markup
+    navListHTML = navList.sort((a,b) => a.order - b.order).map(function(n) {
+        if((n.priority  < 2)){
+            return n.html;
+        } else {
+            secondaryNav.push(n.html);
+        }
+    });
+    navListHTML.push(secondaryNavHTML);
+    return navListHTML;
+}
diff --git a/skyquake/framework/widgets/skyquake_nav/skyquakeNav.scss b/skyquake/framework/widgets/skyquake_nav/skyquakeNav.scss
new file mode 100644 (file)
index 0000000..76df1ab
--- /dev/null
@@ -0,0 +1,104 @@
+@import '../../style/_colors.scss';
+.active {
+        background-color: $brand-blue!important;
+        border-color: $brand-blue!important;
+        color: #fff!important
+    }
+    .skyquakeNav {
+        display: -ms-flexbox;
+        display: flex;
+        color:white;
+        background:black;
+        position:relative;
+        z-index: 10;
+        font-size:0.75rem;
+        .secondaryNav {
+            -ms-flex: 1 1 auto;
+                flex: 1 1 auto;
+            display: -ms-flexbox;
+            display: flex;
+            -ms-flex-pack: end;
+                justify-content: flex-end;
+        }
+        .app {
+            position:relative;
+            display:-ms-flexbox;
+            display:flex;
+            h2 {
+                font-size:0.75rem;
+                border-right: 1px solid black;
+                display: -ms-flexbox;
+                display: flex;
+                -ms-flex-align: center;
+                    align-items: center;
+                .oi {
+                    padding-right: 0.5rem;
+                }
+            }
+            .menu {
+                position:absolute;
+                display:none;
+                z-index:2;
+                width: 100%;
+            }
+            &:first-child{
+                h2 {
+                    border-left: 1px solid black;
+                }
+            }
+            &:hover {
+                a {
+                    color:$brand-blue-light;
+                    cursor:pointer;
+                }
+                .menu {
+                    display:block;
+                    background:black;
+                    a {
+                        color:white;
+                    }
+                    li:hover {
+                        a {
+                            color:$brand-blue-light;
+                        }
+                    }
+                }
+            }
+            &.active {
+                color:white;
+                background:black;
+                a {
+                    color:white;
+                }
+            }
+        }
+        a{
+            display:block;
+            padding:0.5rem 1rem;
+            text-decoration:none;
+            text-transform:uppercase;
+            color:white;
+        }
+        &:before {
+            content: '';
+            min-width: 5.5rem;
+            margin: 0.125rem 1rem;
+            background: url('../../style/img/svg/osm-logo_color_rgb_white_text.svg') no-repeat center center;
+            background-size: contain;
+        }
+        .userSection {
+            display:-ms-flexbox;
+            display:flex;
+            -ms-flex-align:center;
+            align-items:center;
+            padding-left: 1rem;
+            text-transform:uppercase;
+            border-left:1px solid white;
+            .projectSelect {
+                padding-left: 0.5rem;
+                font-size: 1rem;
+                min-width: 75%;
+                height: 25px;
+            }
+        }
+    }
index 71921a7..8cda802 100644 (file)
@@ -94,9 +94,10 @@ class ProjectManagementDashboard extends React.Component {
         let self = this;
         e.preventDefault();
         e.stopPropagation();
-        let projectUsers = self.state.projectUsers;
-        let cleanUsers = this.cleanUsers(projectUsers);
         let projectName = self.state['name'];
+        let projectUsers = self.state.projectUsers;
+        let cleanUsers = this.cleanUsers(projectUsers, projectName);
+
 
         this.Store.createProject({
             'name': projectName,
@@ -111,9 +112,10 @@ class ProjectManagementDashboard extends React.Component {
         let self = this;
         e.preventDefault();
         e.stopPropagation();
+         let projectName = self.state['name'];
         let projectUsers = self.state.projectUsers;
-        let cleanUsers = this.cleanUsers(projectUsers);
-        let projectName = self.state['name'];
+        let cleanUsers = this.cleanUsers(projectUsers, projectName);
+
 
         this.Store.updateProject(_.merge({
             'name': projectName,
@@ -124,7 +126,7 @@ class ProjectManagementDashboard extends React.Component {
             }
         }));
     }
-    cleanUsers(projectUsers) {
+    cleanUsers(projectUsers, projectName) {
         let cleanUsers = [];
         //Remove null values from role
         projectUsers.map((u) => {
@@ -132,8 +134,10 @@ class ProjectManagementDashboard extends React.Component {
            u.role && u.role.map((r,i) => {
              let role = {};
              //you may add a user without a role or a keys, but if one is present then the other must be as well.
-            if(!r.role || ( !r || ((r.role || r['keys']) && (!r.role || !r['keys'])))) {
+            if(!r.role ) {
             } else {
+                delete r.keys;
+                    // r.keys = projectName;
                     cleanRoles.push(r)
             }
            });
index ce0c984..55646e8 100644 (file)
@@ -25,11 +25,16 @@ module.exports = function(Alt) {
                   type: 'GET',
                   beforeSend: Utils.addAuthorizationStub,
                   success: function(data, textStatus, jqXHR) {
-                    resolve(data.users);
+                    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);
                 });
               });
           },
@@ -53,6 +58,11 @@ module.exports = function(Alt) {
                 }).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);
                 });
               });
           },
@@ -77,6 +87,11 @@ module.exports = function(Alt) {
                 }).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);
                 });
             });
           },
@@ -100,6 +115,11 @@ module.exports = function(Alt) {
               }).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);
               });
             });
           },
@@ -125,6 +145,11 @@ module.exports = function(Alt) {
                 }).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);
                 });
               });
             },
index 1cb2088..17a5a59 100644 (file)
@@ -15,7 +15,7 @@ export default class ProjectManagementStore {
         this.projectUsers = [];
         this.selectedUser = null;
         this.selectedRole = null;
-        this.roles = ['rw-rbac-platform:platform-admin', 'rw-rbac-platform:platform-oper', 'rw-rbac-platform:super-admin'
+        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 = [];
@@ -147,17 +147,17 @@ export default class ProjectManagementStore {
         });
     }
     handleAddUser(e) {
+        let self = this;
         let u = JSON.parse(this.selectedUser);
         let r = this.selectedRole;
         let projectUsers = this.projectUsers;
-        let keys = ',';
         console.log('adding user')
         projectUsers.push({
           'user-name': u['user-name'],
           'user-domain': u['user-domain'],
           "role":[{
                       "role": r,
-                      "keys": keys
+                      "keys": self.name
             }
           ]
         })
@@ -168,16 +168,14 @@ export default class ProjectManagementStore {
         let {userIndex, roleIndex, checked} = data;
         let projectUsers = this.projectUsers;
         let selectedRole = self.roles[roleIndex];
-        let keys = ',';
         if(checked) {
             if(!projectUsers[userIndex].role) projectUsers[userIndex].role = [];
             projectUsers[userIndex].role.push({
-                role: self.roles[roleIndex],
-                keys: keys
+                role: self.roles[roleIndex]
             })
         } else {
             let role = projectUsers[userIndex].role;
-            let roleIndex = _.findIndex(role, {role:selectedRole, keys: keys})
+            let roleIndex = _.findIndex(role, {role:selectedRole})
             projectUsers[userIndex].role.splice(roleIndex, 1)
         }
        self.setState({projectUsers});
@@ -186,9 +184,7 @@ export default class ProjectManagementStore {
     handleUpdateUserRoleInProject(data) {
         let {userIndex, roleIndex, value} = data;
         let projectUsers = this.projectUsers;
-        let keys = ',';
         projectUsers[userIndex].role[roleIndex].role = value;
-        projectUsers[userIndex].role[roleIndex]['keys'] = keys;
 
     }
     addRoleToUserInProject(userIndex) {
@@ -196,11 +192,8 @@ export default class ProjectManagementStore {
         if(!projectUsers[userIndex].role) {
             projectUsers[userIndex].role = [];
         }
-        let keys = ',';
         projectUsers[userIndex].role.push({
-              'role': null,
-              //temp until we get actual keys
-              'keys' : keys
+              'role': null
             });
         this.setState({
             projectUsers
index 0275831..f946cbb 100644 (file)
@@ -79,7 +79,7 @@ class UserManagementDashboard extends React.Component {
     cancelEditUser = () => {
         this.actions.editUser(true)
     }
-    closePanel = () => {
+    osePanel = () => {
         this.actions.handleCloseUserPanel();
     }
     // updateUser = (e) => {
index a5c6242..08460ba 100644 (file)
@@ -25,7 +25,7 @@ module.exports = function(Alt) {
                   type: 'GET',
                   beforeSend: Utils.addAuthorizationStub,
                   success: function(data, textStatus, jqXHR) {
-                    resolve(data.users);
+                    resolve(data.user);
                   }
                 }).fail(function(xhr){
                   //Authentication and the handling of fail states should be wrapped up into a connection class.
index ad6e818..2e1f3c0 100644 (file)
@@ -25,7 +25,7 @@ module.exports = function(Alt) {
                   type: 'GET',
                   beforeSend: Utils.addAuthorizationStub,
                   success: function(data, textStatus, jqXHR) {
-                    resolve(data.users);
+                    resolve(data.user);
                   }
                 }).fail(function(xhr){
                   //Authentication and the handling of fail states should be wrapped up into a connection class.
index 38dd1ce..326f13a 100644 (file)
@@ -150,14 +150,12 @@ export default class PlatformRoleManagementStore {
         let u = JSON.parse(this.selectedUser);
         let r = this.selectedRole;
         let platformUsers = this.platformUsers;
-        let keys = ","
         console.log('adding user')
         platformUsers.push({
           'user-name': u['user-name'],
           'user-domain': u['user-domain'],
           "role":[{
-                      "role": r,
-                      "keys": keys
+                      "role": r
             }
           ]
         })
@@ -168,16 +166,13 @@ export default class PlatformRoleManagementStore {
         let {userIndex, roleIndex, checked} = data;
         let platformUsers = this.platformUsers;
         let selectedRole = self.roles[roleIndex];
-        let keys = ","
         if(checked) {
             if(!platformUsers[userIndex].role) platformUsers[userIndex].role = [];
             platformUsers[userIndex].role.push({
-                role: selectedRole,
-                keys: keys
+                role: selectedRole
             })
         } else {
             let role = platformUsers[userIndex].role;
-            let roleIndex = _.findIndex(role, {role:selectedRole, keys: keys})
             platformUsers[userIndex].role.splice(roleIndex, 1)
         }
        self.setState({platformUsers});
@@ -187,7 +182,6 @@ export default class PlatformRoleManagementStore {
         let {userIndex, roleIndex, value} = data;
         let platformUsers = this.platformUsers;
         platformUsers[userIndex].role[roleIndex].role = value;
-        platformUsers[userIndex].role[roleIndex]['keys'] = value;
 
     }
     addRoleToUserInProject(userIndex) {
@@ -196,9 +190,7 @@ export default class PlatformRoleManagementStore {
             platformUsers[userIndex].role = [];
         }
         platformUsers[userIndex].role.push({
-              'role': null,
-              //temp until we get actual keys
-              'keys' : ','
+              'role': null
             });
         this.setState({
             platformUsers