NOTICKET: Merging OSM/master to OSM/projects
authorKIRAN KASHALKAR <kiran.kashalkar@riftio.com>
Thu, 20 Apr 2017 13:18:13 +0000 (09:18 -0400)
committerKIRAN KASHALKAR <kiran.kashalkar@riftio.com>
Thu, 20 Apr 2017 13:18:13 +0000 (09:18 -0400)
Signed-off-by: KIRAN KASHALKAR <kiran.kashalkar@riftio.com>
126 files changed:
skyquake/.storybook/config.js
skyquake/framework/core/api_utils/constants.js
skyquake/framework/core/api_utils/sockets.js
skyquake/framework/core/api_utils/utils.js
skyquake/framework/core/modules/api/descriptorModelMetaAPI.js
skyquake/framework/core/modules/api/projectManagementAPI.js [new file with mode: 0644]
skyquake/framework/core/modules/api/restconf.js
skyquake/framework/core/modules/api/sessions.js [new file with mode: 0644]
skyquake/framework/core/modules/api/userManagementAPI.js [new file with mode: 0644]
skyquake/framework/core/modules/navigation_manager.js
skyquake/framework/core/modules/routes/configuration.js
skyquake/framework/core/modules/routes/navigation.js
skyquake/framework/core/modules/routes/projectManagement.js [new file with mode: 0644]
skyquake/framework/core/modules/routes/sessions.js [new file with mode: 0644]
skyquake/framework/core/modules/routes/userManagement.js [new file with mode: 0644]
skyquake/framework/core/views/login.html [new file with mode: 0644]
skyquake/framework/style/_colors.scss
skyquake/framework/utils/roleConstants.js [new file with mode: 0644]
skyquake/framework/utils/utils.js
skyquake/framework/widgets/button/button.scss
skyquake/framework/widgets/button/sq-button.jsx
skyquake/framework/widgets/components.js
skyquake/framework/widgets/form_controls/formControls.jsx [new file with mode: 0644]
skyquake/framework/widgets/form_controls/formControls.scss
skyquake/framework/widgets/form_controls/input.jsx [new file with mode: 0644]
skyquake/framework/widgets/form_controls/selectOption.jsx
skyquake/framework/widgets/form_controls/textInput.jsx
skyquake/framework/widgets/header/header.scss
skyquake/framework/widgets/panel/panel.jsx
skyquake/framework/widgets/panel/panel.scss
skyquake/framework/widgets/skyquake_container/skyquakeApp.scss
skyquake/framework/widgets/skyquake_container/skyquakeComponent.jsx
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/framework/widgets/skyquake_rbac/skyquakeRBAC.jsx [new file with mode: 0644]
skyquake/package.json
skyquake/plugins/CMakeLists.txt
skyquake/plugins/about/api/about.js
skyquake/plugins/about/config.json
skyquake/plugins/accounts/api/accounts.js
skyquake/plugins/accounts/api/cloud_account/cloudAccount.js
skyquake/plugins/accounts/api/config_agent/configAgent.js
skyquake/plugins/accounts/api/sdn_account/sdnAccount.js
skyquake/plugins/accounts/config.json
skyquake/plugins/accounts/src/account/account.jsx
skyquake/plugins/accounts/src/account/accountStore.js
skyquake/plugins/accounts/src/account/accountsDashboard.jsx
skyquake/plugins/accounts/src/account_sidebar/accountSidebar.jsx
skyquake/plugins/composer/api/composer.js
skyquake/plugins/composer/api/packageFileHandler.js
skyquake/plugins/composer/config.json
skyquake/plugins/composer/src/src/components/Button.js
skyquake/plugins/composer/src/src/components/CatalogItemDetailsEditor.js
skyquake/plugins/composer/src/src/components/CatalogPanel.js
skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js
skyquake/plugins/composer/src/src/components/ComposerApp.js
skyquake/plugins/composer/src/src/components/ComposerAppToolbar.js
skyquake/plugins/composer/src/src/components/DetailsPanel.js
skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js
skyquake/plugins/composer/src/src/components/EditorForwardingGraph/EditForwardingGraphPaths.js
skyquake/plugins/composer/src/src/libraries/PackageManagerApi.js [deleted file]
skyquake/plugins/composer/src/src/libraries/utils.js
skyquake/plugins/composer/src/src/styles/Button.scss
skyquake/plugins/config/api/ro.js
skyquake/plugins/config/config.json
skyquake/plugins/debug/api/debug.js
skyquake/plugins/debug/config.json
skyquake/plugins/launchpad/api/launchpad.js
skyquake/plugins/launchpad/config.json
skyquake/plugins/launchpad/package.json
skyquake/plugins/launchpad/src/instantiate/instantiateDashboard.jsx
skyquake/plugins/launchpad/src/launchpad-create.js [deleted file]
skyquake/plugins/launchpad/src/launchpad.jsx
skyquake/plugins/launchpad/src/launchpad_card/launchpad-card.js [deleted file]
skyquake/plugins/launchpad/src/launchpad_card/launchpadCard.jsx
skyquake/plugins/launchpad/src/launchpad_card/launchpadCardCloudAccount.jsx
skyquake/plugins/launchpad/src/launchpad_card/vnfrConfigPrimitives.jsx
skyquake/plugins/launchpad/src/nsCardPanel/nsCardPanel.jsx
skyquake/plugins/launchpad/src/recordViewer/recordCard.jsx
skyquake/plugins/launchpad/src/recordViewer/recordView.jsx
skyquake/plugins/logging/api/logging.js
skyquake/plugins/logging/api/transforms.js
skyquake/plugins/logging/config.json
skyquake/plugins/project_management/CMakeLists.txt [new file with mode: 0644]
skyquake/plugins/project_management/config.json [new file with mode: 0644]
skyquake/plugins/project_management/package.json [new file with mode: 0644]
skyquake/plugins/project_management/routes.js [new file with mode: 0644]
skyquake/plugins/project_management/scripts/build.sh [new file with mode: 0755]
skyquake/plugins/project_management/scripts/install.sh [new file with mode: 0755]
skyquake/plugins/project_management/server.js [new file with mode: 0644]
skyquake/plugins/project_management/src/dashboard/dashboard.jsx [new file with mode: 0644]
skyquake/plugins/project_management/src/dashboard/projectMgmt.scss [new file with mode: 0644]
skyquake/plugins/project_management/src/dashboard/projectMgmtActions.js [new file with mode: 0644]
skyquake/plugins/project_management/src/dashboard/projectMgmtSource.js [new file with mode: 0644]
skyquake/plugins/project_management/src/dashboard/projectMgmtStore.js [new file with mode: 0644]
skyquake/plugins/project_management/src/main.js [new file with mode: 0644]
skyquake/plugins/project_management/webpack.production.config.js [new file with mode: 0644]
skyquake/plugins/user_management/CMakeLists.txt [new file with mode: 0644]
skyquake/plugins/user_management/config.json [new file with mode: 0644]
skyquake/plugins/user_management/package.json [new file with mode: 0644]
skyquake/plugins/user_management/routes.js [new file with mode: 0644]
skyquake/plugins/user_management/scripts/build.sh [new file with mode: 0755]
skyquake/plugins/user_management/scripts/install.sh [new file with mode: 0755]
skyquake/plugins/user_management/server.js [new file with mode: 0644]
skyquake/plugins/user_management/src/dashboard/dashboard.jsx [new file with mode: 0644]
skyquake/plugins/user_management/src/dashboard/userMgmt.scss [new file with mode: 0644]
skyquake/plugins/user_management/src/dashboard/userMgmtActions.js [new file with mode: 0644]
skyquake/plugins/user_management/src/dashboard/userMgmtSource.js [new file with mode: 0644]
skyquake/plugins/user_management/src/dashboard/userMgmtStore.js [new file with mode: 0644]
skyquake/plugins/user_management/src/main.js [new file with mode: 0644]
skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagement.jsx [new file with mode: 0644]
skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagement.scss [new file with mode: 0644]
skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementActions.js [new file with mode: 0644]
skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementSource.js [new file with mode: 0644]
skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementStore.js [new file with mode: 0644]
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]
skyquake/plugins/user_management/webpack.production.config.js [new file with mode: 0644]
skyquake/skyquake.js

index 42edf3c..04aee50 100644 (file)
@@ -6,8 +6,11 @@ function loadStories() {
   // require('../tests/stories/sshKeyCard');
   // require('../tests/stories/button');
   // require('../tests/stories/sq-input-slider');
-  require('../tests/stories/catalogCard');
+  // require('../tests/stories/catalogCard');
+  require('../tests/stories/inputs');
   // require as many stories as you need.
 }
 
 configure(loadStories, module);
+
+
index 0aac7d2..7f54797 100644 (file)
@@ -73,6 +73,7 @@ constants.SOCKET_BASE_PORT = 3500;
 constants.SOCKET_POOL_LENGTH = 20;
 constants.SERVER_PORT = process.env.SERVER_PORT || 8000;
 constants.SECURE_SERVER_PORT = process.env.SECURE_SERVER_PORT || 8443;
+constants.REJECT_UNAUTHORIZED = false;
 
 constants.BASE_PACKAGE_UPLOAD_DESTINATION = 'upload/packages/';
 constants.PACKAGE_MANAGER_SERVER_PORT = 4567;
index 5e0b25b..2e6779c 100644 (file)
@@ -33,6 +33,7 @@ var url = require('url');
 var sockjs = require('sockjs');
 var websocket_multiplex = require('websocket-multiplex');
 var utils = require('./utils.js');
+var configurationAPI = require('../modules/api/configuration.js');
 
 
 var Subscriptions = function() {
@@ -91,7 +92,6 @@ Subscriptions.prototype.subscribe = function(req, callback) {
     var a = url.resolve(origin, req.baseUrl);
     var b = url.resolve(a, URL);
     URL = b;
-    console.log('DEBUG URL IS', URL);
   } else {
     protocol = protocol[1]
   }
@@ -278,12 +278,12 @@ function PollingSocket(url, req, interval, config) {
   self.isClosed = false;
   var requestHeaders = {};
   _.extend(requestHeaders, {
-    'Authorization': req.get('Authorization')
+    Cookie: req.get('Cookie')
   });
 
   var pollServer = function() {
     Request({
-      url: url,
+      url: utils.projectContextUrl(req, url),
       method: config.method || 'GET',
       headers: requestHeaders,
       json: config.payload,
index 5b17279..3a50964 100644 (file)
@@ -49,6 +49,40 @@ var confdPort = function(api_server) {
        return api_server + ':' + CONFD_PORT;
 };
 
+var projectContextUrl = function(req, url) {
+       //NOTE: We need to go into the sessionStore because express-session
+       // does not reliably update the session.
+       // See https://github.com/expressjs/session/issues/450
+       var projectId = (req.session &&
+                                        req.sessionStore &&
+                                        req.sessionStore.sessions &&
+                                        req.sessionStore.sessions[req.session.id] &&
+                                        JSON.parse(req.sessionStore.sessions[req.session.id])['projectId']) ||
+                                        (null);
+       if (projectId) {
+               return url.replace(/(\/api\/operational\/|\/api\/config\/)(.*)/, '$1project/' + projectId + '/$2');
+       }
+       return url;
+}
+
+var addProjectContextToRPCPayload = function(req, url, inputPayload) {
+       //NOTE: We need to go into the sessionStore because express-session
+       // does not reliably update the session.
+       // See https://github.com/expressjs/session/issues/450
+       var projectId = (req.session &&
+                                        req.sessionStore &&
+                                        req.sessionStore.sessions &&
+                                        req.sessionStore.sessions[req.session.id] &&
+                                        JSON.parse(req.sessionStore.sessions[req.session.id])['projectId']) ||
+                                        (null);
+       if (projectId) {
+               if (url.indexOf('/api/operations/')) {
+                       inputPayload['project-name'] = projectId;
+               }
+       }
+       return inputPayload;
+}
+
 
 var validateResponse = function(callerName, error, response, body, resolve, reject) {
        var res = {};
@@ -61,12 +95,12 @@ var validateResponse = function(callerName, error, response, body, resolve, reje
                };
                reject(res);
                return false;
-       } else if (response.statusCode >= 400) {
+       } else if (response.statusCode >= CONSTANTS.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST) {
                console.log('Problem with "', callerName, '": ', response.statusCode, ':', body);
                res.statusCode = response.statusCode;
 
                // auth specific
-               if (response.statusCode == 401) {
+               if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.ERROR.UNAUTHORIZED) {
                        res.errorMessage = {
                                error: 'Authentication needed' + body
                        };
@@ -81,7 +115,7 @@ var validateResponse = function(callerName, error, response, body, resolve, reje
 
                reject(res);
                return false;
-       } else if (response.statusCode == 204) {
+       } else if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT) {
                resolve({
                        statusCode: response.statusCode,
                        data: {}
@@ -95,7 +129,7 @@ var validateResponse = function(callerName, error, response, body, resolve, reje
 
 var checkAuthorizationHeader = function(req) {
        return new Promise(function(resolve, reject) {
-               if (req.get('Authorization') == null) {
+               if (req.session && req.session.authorization == null) {
                        reject();
                } else {
                        resolve();
@@ -119,12 +153,12 @@ if (process.env.LOG_REQUESTS) {
                                reject(res);
                                fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error: ' + error);
                                return false;
-                       } else if (response.statusCode >= 400) {
+                       } else if (response.statusCode >= CONSTANTS.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST) {
                                console.log('Problem with "', callerName, '": ', response.statusCode, ':', body);
                                res.statusCode = response.statusCode;
 
                                // auth specific
-                               if (response.statusCode == 401) {
+                               if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.ERROR.UNAUTHORIZED) {
                                        res.errorMessage = {
                                                error: 'Authentication needed' + body
                                        };
@@ -140,7 +174,7 @@ if (process.env.LOG_REQUESTS) {
                                reject(res);
                                fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error Body: ' + body);
                                return false;
-                       } else if (response.statusCode == 204) {
+                       } else if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT) {
                                resolve();
                                fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Response Body: ' + body);
                                return false;
@@ -197,10 +231,10 @@ var passThroughConstructor = function(app) {
                }
                new Promise(function(resolve, reject) {
                        request({
-                               uri: uri,
+                               uri: projectContextUrl(req, uri),
                                method: 'GET',
                                headers: _.extend({}, CONSTANTS.HTTP_HEADERS.accept[type], {
-                                       'Authorization': req.get('Authorization'),
+                                       'Authorization': req.session && req.session.authorization,
                                        forever: CONSTANTS.FOREVER_ON,
                                        rejectUnauthorized: false,
                                })
@@ -244,5 +278,9 @@ module.exports = {
 
     passThroughConstructor: passThroughConstructor,
 
-    getPortForProtocol: getPortForProtocol
+    getPortForProtocol: getPortForProtocol,
+
+    projectContextUrl: projectContextUrl,
+
+    addProjectContextToRPCPayload: addProjectContextToRPCPayload
 };
index b0223b2..34d30b3 100644 (file)
@@ -36,7 +36,7 @@ ModelMeta.get = function(req) {
                 uri: utils.confdPort(api_server) + '/api/schema/nsd-catalog/nsd',
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
@@ -46,7 +46,7 @@ ModelMeta.get = function(req) {
                 uri: utils.confdPort(api_server) + '/api/schema/vnfd-catalog/vnfd',
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
diff --git a/skyquake/framework/core/modules/api/projectManagementAPI.js b/skyquake/framework/core/modules/api/projectManagementAPI.js
new file mode 100644 (file)
index 0000000..c00de32
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ *
+ *   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.
+ *
+ */
+// DescriptorModelMeta API (NSD + VNFD)
+
+
+var ProjectManagement = {};
+var Promise = require('bluebird');
+var rp = require('request-promise');
+var Promise = require('promise');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var _ = require('lodash');
+
+ProjectManagement.get = function(req) {
+    var self = this;
+    var api_server = req.query['api_server'];
+
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            rp({
+                uri: utils.confdPort(api_server) + '/api/operational/project',
+                method: 'GET',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                    'Authorization': req.session && req.session.authorization
+                }),
+                forever: constants.FOREVER_ON,
+                rejectUnauthorized: false,
+                resolveWithFullResponse: true
+            })
+        ]).then(function(result) {
+            var response = {};
+            response['data'] = {};
+            if (result[0].body) {
+                response['data']['project'] = JSON.parse(result[0].body)['rw-project:project'];
+            }
+            response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+            resolve(response);
+        }).catch(function(error) {
+            var response = {};
+            console.log('Problem with ProjectManagement.get', error);
+            response.statusCode = error.statusCode || 500;
+            response.errorMessage = {
+                error: 'Failed to get ProjectManagement' + error
+            };
+            reject(response);
+        });
+    });
+};
+
+ProjectManagement.create = function(req) {
+    var self = this;
+    var api_server = req.query['api_server'];
+    var data = req.body;
+    data = {
+        "project":[data]
+    }
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            rp({
+                uri: utils.confdPort(api_server) + '/api/config/project',
+                method: 'POST',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                    'Authorization': req.session && req.session.authorization
+                }),
+                forever: constants.FOREVER_ON,
+                json: data,
+                rejectUnauthorized: false,
+                resolveWithFullResponse: true
+            })
+        ]).then(function(result) {
+            var response = {};
+            response['data'] = {};
+            if (result[0].body) {
+                response['data'] = result[0].body;
+            }
+            response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+            resolve(response);
+        }).catch(function(error) {
+            var response = {};
+            console.log('Problem with ProjectManagement.create', error);
+            response.statusCode = error.statusCode || 500;
+            response.errorMessage = {
+                error: 'Failed to create user' + error
+            };
+            reject(response);
+        });
+    });
+};
+ProjectManagement.update = function(req) {
+    var self = this;
+    var api_server = req.query['api_server'];
+    var bodyData = req.body;
+    data = {
+        "project":[bodyData]
+    }
+    var updateTasks = [];
+
+    var updateUser = rp({
+                uri: utils.confdPort(api_server) + '/api/config/project',
+                method: 'PUT',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                    'Authorization': req.session && req.session.authorization
+                }),
+                forever: constants.FOREVER_ON,
+                json: data,
+                rejectUnauthorized: false,
+                resolveWithFullResponse: true
+            });
+    updateTasks.push(updateUser)
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            updateTasks
+        ]).then(function(result) {
+            var response = {};
+            response['data'] = {};
+            if (result[0].body) {
+                response['data'] = result[0].body;
+            }
+            response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+            resolve(response);
+        }).catch(function(error) {
+            var response = {};
+            console.log('Problem with ProjectManagement.update', error);
+            response.statusCode = error.statusCode || 500;
+            response.errorMessage = {
+                error: 'Failed to passwordChange user' + error
+            };
+            reject(response);
+        });
+    });
+};
+
+ProjectManagement.delete = function(req) {
+    var self = this;
+    var projectname = req.params.projectname;
+    var api_server = req.query["api_server"];
+    var requestHeaders = {};
+    var url = `${utils.confdPort(api_server)}/api/config/project/${projectname}`
+    return new Promise(function(resolve, reject) {
+        _.extend(requestHeaders,
+            constants.HTTP_HEADERS.accept.data,
+            constants.HTTP_HEADERS.content_type.data, {
+                'Authorization': req.session && req.session.authorization
+            });
+        rp({
+            url: url,
+            method: 'DELETE',
+            headers: requestHeaders,
+            forever: constants.FOREVER_ON,
+            rejectUnauthorized: false,
+        }, function(error, response, body) {
+            if (utils.validateResponse('ProjectManagement.DELETE', error, response, body, resolve, reject)) {
+                return resolve({
+                    statusCode: response.statusCode,
+                    data: JSON.stringify(response.body)
+                });
+            };
+        });
+    })
+}
+
+
+ProjectManagement.getPlatform = function(req, userId) {
+    var self = this;
+    var api_server = req.query['api_server'];
+    var user = req.params['userId'] || userId;
+    return new Promise(function(resolve, reject) {
+        var url = utils.confdPort(api_server) + '/api/operational/rbac-platform-config';
+        if(user) {
+            url = url + '/user/' + user;
+        }
+        Promise.all([
+            rp({
+                uri: url,
+                method: 'GET',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                    'Authorization': req.session && req.session.authorization
+                }),
+                forever: constants.FOREVER_ON,
+                rejectUnauthorized: false,
+                resolveWithFullResponse: true
+            })
+        ]).then(function(result) {
+            var response = {};
+            response['data'] = {};
+            if (result[0].body) {
+                if(user) {
+                    response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:user'];
+                } else {
+                    response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:rbac-platform-config'];
+                }
+            }
+            response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+            resolve(response);
+        }).catch(function(error) {
+            var response = {};
+            console.log('Problem with ProjectManagement.getPlatform', error);
+            response.statusCode = error.statusCode || 500;
+            response.errorMessage = {
+                error: 'Failed to get ProjectManagement.getPlatform' + error
+            };
+            reject(response);
+        });
+    });
+};
+
+ProjectManagement.updatePlatform = function(req) {
+    var self = this;
+    var api_server = req.query['api_server'];
+    var bodyData = req.body;
+    data = bodyData;
+    data.user = JSON.parse(data.user)
+    var updateTasks = [];
+
+    var updateUser = rp({
+                uri: utils.confdPort(api_server) + '/api/config/rbac-platform-config',
+                method: 'PUT',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                    'Authorization': req.session && req.session.authorization
+                }),
+                forever: constants.FOREVER_ON,
+                json: data,
+                rejectUnauthorized: false,
+                resolveWithFullResponse: true
+            });
+    updateTasks.push(updateUser)
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            updateTasks
+        ]).then(function(result) {
+            var response = {};
+            response['data'] = {};
+            if (result[0].body) {
+                response['data'] = result[0].body;
+            }
+            response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+            resolve(response);
+        }).catch(function(error) {
+            var response = {};
+            console.log('Problem with ProjectManagement.updatePlatform', error);
+            response.statusCode = error.statusCode || 500;
+            response.errorMessage = {
+                error: 'Failed to passwordChange user' + error
+            };
+            reject(response);
+        });
+    });
+};
+
+
+module.exports = ProjectManagement;
index 5ba0eb5..03f2721 100644 (file)
@@ -45,7 +45,7 @@ restconfAPI['streams'].get = function(req) {
             url: uri + url + '?deep',
             method: 'GET',
             headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             forever: constants.FOREVER_ON,
             rejectUnauthorized: false,
diff --git a/skyquake/framework/core/modules/api/sessions.js b/skyquake/framework/core/modules/api/sessions.js
new file mode 100644 (file)
index 0000000..9736f88
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ *
+ *   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.
+ *
+ */
+
+/**
+ * sessions api module. Provides API functions for sessions
+ * @module framework/core/modules/api/sessions
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var Promise = require('bluebird');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var request = utils.request;
+var rp = require('request-promise');
+var sessionsAPI = {};
+var _ = require('lodash');
+var base64 = require('base-64');
+var APIVersion = '/v2';
+var configurationAPI = require('./configuration');
+
+function logAndReject(mesg, reject, errCode) {
+    res.errorMessage = {
+        error: mesg
+    }
+    res.statusCode = errCode || constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST;
+    console.log(mesg);
+    reject(res);
+}
+
+function logAndRedirectToLogin(mesg, res, req) {
+    var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server);
+    var upload_server = req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname);
+    console.log(mesg);
+    res.redirect('login.html?api_server=' + api_server + '&upload_server=' + upload_server + '&referer=' + req.headers.referer);
+    res.end();
+}
+
+sessionsAPI.create = function(req, res) {
+    var api_server = req.query["api_server"];
+    var uri = utils.confdPort(api_server);
+    var login_url = uri + APIVersion + '/api/login';
+    var project_url = uri + APIVersion + '/api/operational/project';
+    var authorization_header_string = 'Basic ' + base64.encode(req.body['username'] + ':' + req.body['password']);
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            rp({
+                url: login_url,
+                method: 'POST',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                    'Authorization': authorization_header_string
+                }),
+                forever: constants.FOREVER_ON,
+                rejectUnauthorized: constants.REJECT_UNAUTHORIZED,
+                resolveWithFullResponse: true
+            }),
+            rp({
+                url: project_url,
+                method: 'GET',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
+                    'Authorization': authorization_header_string
+                }),
+                forever: constants.FOREVER_ON,
+                rejectUnauthorized: constants.REJECT_UNAUTHORIZED,
+                resolveWithFullResponse: true
+            })
+
+        ]).then(function(results) {
+            // results[0].statusCode => 200/201
+            // results[1].body.collection['rw-project:project'] => List of projects OR 204 with no content
+            if (results[0].statusCode != constants.HTTP_RESPONSE_CODES.SUCCESS.OK) {
+                var errorMsg = 'Invalid credentials provided!';
+                logAndRedirectToLogin(errorMsg, res, req);
+                return;
+            }
+
+            var username = req.body['username'];
+            var project_list_for_user = [];
+
+            if (results[1].statusCode == constants.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT) {
+                console.log('No projects added or user ', username ,' not privileged to view projects.');
+            } else {
+                // go through projects and get list of projects that this user belongs to.
+                // pick first one as default project?
+                var isLCM = false;
+                var projects = JSON.parse(results[1].body).collection['rw-project:project'];
+                projects && projects.map(function(project) {
+                    project['project-config'] &&
+                    project['project-config']['user'] &&
+                    project['project-config']['user'].map(function(user) {
+                        if (user['user-name'] == username) {
+                            project_list_for_user.push(project);
+                            user.role.map(function(role) {
+                                if(role.role.indexOf('rw-project-mano:lcm') > -1) {
+                                    isLCM = true;
+                                }
+                            })
+                        }
+                    });
+                });
+                if (project_list_for_user.length > 0) {
+                    req.session.projectId = project_list_for_user.sort() && project_list_for_user[0].name;
+                    req.session.isLCM = isLCM;
+                }
+            }
+
+            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.' : '';
+
+            console.log(successMsg);
+
+            var response = {
+                statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.CREATED,
+                data: JSON.stringify({
+                    status: successMsg
+                })
+            };
+
+            req.session.save(function(err) {
+                if (err) {
+                    console.log('Error saving session to store', err);
+                }
+            })
+
+            resolve(response);
+
+        }).catch(function(error) {
+            // Something went wrong - Redirect to /login
+            var errorMsg = 'Error logging in or getting list of projects. Error: ' + error;
+            console.log(errorMsg);
+            logAndRedirectToLogin(errorMsg, res, req);
+        });
+    })
+};
+
+sessionsAPI.addProjectToSession = function(req, res) {
+    return new Promise(function(resolve, reject) {
+        if (req.session && req.session.loggedIn == true) {
+            req.session.projectId = req.params.projectId;
+            req.session.save(function(err) {
+                if (err) {
+                    console.log('Error saving session to store', err);
+                }
+                var successMsg = 'Added project ' + req.session.projectId + ' to session ' + req.sessionID;
+                console.log(successMsg);
+
+                return resolve ({
+                    statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+                    data: JSON.stringify({
+                        status: successMsg
+                    })
+                });
+
+                var errorMsg = 'Session does not exist or not logged in';
+                logAndReject(errorMsg, reject, constants.HTTP_RESPONSE_CODES.ERROR.NOT_FOUND);
+            });
+        }
+    });
+}
+
+sessionsAPI.delete = function(req, res) {
+    var api_server = req.query["api_server"];
+    var uri = utils.confdPort(api_server);
+    var url = uri + '/api/logout';
+    req.returnTo = req.headers.referer;
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            rp({
+                url: url,
+                method: 'POST',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                    'Authorization': req.session.authorization
+                }),
+                forever: constants.FOREVER_ON,
+                rejectUnauthorized: constants.REJECT_UNAUTHORIZED,
+                resolveWithFullResponse: true
+            }),
+            new Promise(function(success, failure) {
+                req.session.destroy(function(err) {
+                    if (err) {
+                        var errorMsg = 'Error deleting session. Error: ' + err;
+                        console.log(errorMsg);
+                        success({
+                            status: 'error',
+                            message: errorMsg
+                        });
+                    }
+
+                    var successMsg = 'Success deleting session';
+                    console.log(successMsg);
+
+                    success({
+                        status: 'success',
+                        message: successMsg
+                    });
+                });
+            })
+        ]).then(function(result) {
+            // assume the session was deleted!
+            var message = 'Session was deleted.'
+            logAndRedirectToLogin(message, res, req);
+
+        }).catch(function(error) {
+            var message = 'Error deleting session or logging out. Error:' + error;
+            logAndRedirectToLogin(message, res, req);
+        });
+    });
+}
+
+
+module.exports = sessionsAPI;
diff --git a/skyquake/framework/core/modules/api/userManagementAPI.js b/skyquake/framework/core/modules/api/userManagementAPI.js
new file mode 100644 (file)
index 0000000..b0b8ad5
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ *
+ *   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.
+ *
+ */
+// DescriptorModelMeta API (NSD + VNFD)
+
+
+var UserManagement = {};
+var Promise = require('bluebird');
+var rp = require('request-promise');
+var Promise = require('promise');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var _ = require('lodash');
+var ProjectManagementAPI = require('./projectManagementAPI.js');
+
+UserManagement.get = function(req) {
+    var self = this;
+    var api_server = req.query['api_server'];
+
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            rp({
+                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
+                }),
+                forever: constants.FOREVER_ON,
+                rejectUnauthorized: false,
+                resolveWithFullResponse: true
+            })
+        ]).then(function(result) {
+            var response = {};
+            response['data'] = {};
+            if (result[0].body) {
+                response['data']['user'] = JSON.parse(result[0].body)['rw-user:user'];
+            }
+            response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+            resolve(response);
+        }).catch(function(error) {
+            var response = {};
+            console.log('Problem with UserManagement.get', error);
+            response.statusCode = error.statusCode || 500;
+            response.errorMessage = {
+                error: 'Failed to get UserManagement' + error
+            };
+            reject(response);
+        });
+    });
+};
+
+
+UserManagement.getProfile = function(req) {
+    var self = this;
+    var api_server = req.query['api_server'];
+    return new Promise(function(resolve, reject) {
+        var response = {};
+        var userId = req.session.userdata.username
+        response['data'] = {
+            userId: userId,
+            projectId: req.session.projectId
+        };
+        UserManagement.getUserInfo(req, userId).then(function(result) {
+            response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK;
+            response.data.data =result.data
+            resolve(response);
+        }, function(error) {
+            console.log('Error retrieving getUserInfo');
+            response.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.INTERNAL_SERVER_ERROR;
+            reject(response);
+        })
+
+    });
+};
+UserManagement.getUserInfo = function(req, userId, domain) {
+    var self = this;
+    var api_server = req.query['api_server'];
+    var id = req.params['userId'] || userId;
+    var domain = req.params['domainId'] || domain;
+    var response = {};
+    return new Promise(function(resolve, reject) {
+        if (id) {
+            var getProjects = ProjectManagementAPI.get(req)
+            var getPlatformUser = ProjectManagementAPI.getPlatform(req, id)
+            Promise.all([
+                getProjects,
+                getPlatformUser
+            ]).then(function(result) {
+                var userData = {
+                    platform: {
+                        role: {
+
+                        }
+                    },
+                    //id/key values for each project
+                    projectId:[],
+                    project: {
+                        /**
+                         *  [projectId] : {
+                         *      data: [project object],
+                         *      role: {
+                         *          [roleId]: true
+                         *      }
+                         *  }
+                         */
+                    }
+                }
+                //Build project roles
+                var projects = result[0].data.project;
+                var userProjects = [];
+                projects && projects.map(function(p, i) {
+                    var users = p['project-config'] && p['project-config'].user;
+                    userData.projectId.push(p.name);
+                    users && users.map(function(u) {
+                        if(u['user-name'] == id) {
+                            userData.project[p.name] = {
+                                data: p,
+                                role: {}
+                            }
+                            u.role && u.role.map(function(r) {
+                                userData.project[p.name].role[r.role] = true
+                            });
+                        }
+                    })
+                });
+                //Build platform roles
+                var platformRoles = result[1].data.platform && result[1].data.platform.role;
+                platformRoles && platformRoles.map(function(r) {
+                    userData.platform.role[r.role] = true
+                });
+                response.data = userData;
+                response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+                resolve(response);
+            })
+        } else {
+            var errorMsg = 'userId not specified in UserManagement.getUserInfo';
+            console.error(errorMsg);
+            response.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST;
+            response.error = errorMsg;
+            reject(response)
+        }
+
+    })
+}
+UserManagement.create = function(req) {
+    var self = this;
+    var api_server = req.query['api_server'];
+    var data = req.body;
+    data = {
+        "user":[data]
+    }
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            rp({
+                uri: utils.confdPort(api_server) + '/api/config/user-config',
+                method: 'POST',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                    'Authorization': req.session && req.session.authorization
+                }),
+                forever: constants.FOREVER_ON,
+                json: data,
+                rejectUnauthorized: false,
+                resolveWithFullResponse: true
+            })
+        ]).then(function(result) {
+            var response = {};
+            response['data'] = {};
+            if (result[0].body) {
+                response['data'] = result[0].body;
+            }
+            response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+            resolve(response);
+        }).catch(function(error) {
+            var response = {};
+            console.log('Problem with UserManagement.create', error);
+            response.statusCode = error.statusCode || 500;
+            response.errorMessage = {
+                error: 'Failed to create user' + error
+            };
+            reject(response);
+        });
+    });
+};
+UserManagement.update = function(req) {
+    var self = this;
+    var api_server = req.query['api_server'];
+    var bodyData = req.body;
+    data = {
+        "user":[bodyData]
+    }
+    var updateTasks = [];
+    if(bodyData.hasOwnProperty('old-password')) {
+        var changePW = rp({
+            uri: utils.confdPort(api_server) + '/api/operations/change-password',
+            method: 'POST',
+            headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                'Authorization': req.session && req.session.authorization
+            }),
+            forever: constants.FOREVER_ON,
+            json: {
+                "input": {
+                    'user-name' : bodyData['user-name'],
+                    'user-domain' : bodyData['user-domain'],
+                    'old-password' : bodyData['old-password'],
+                    'new-password' : bodyData['new-password'],
+                    'confirm-password' : bodyData['confirm-password'],
+                }
+            },
+            rejectUnauthorized: false,
+            resolveWithFullResponse: true
+        });
+        updateTasks.push(changePW);
+    };
+    var updateUser = rp({
+                uri: utils.confdPort(api_server) + '/api/config/user-config',
+                method: 'PUT',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+                    'Authorization': req.session && req.session.authorization
+                }),
+                forever: constants.FOREVER_ON,
+                json: data,
+                rejectUnauthorized: false,
+                resolveWithFullResponse: true
+            });
+    updateTasks.push(updateUser)
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            updateTasks
+        ]).then(function(result) {
+            var response = {};
+            response['data'] = {};
+            if (result[0].body) {
+                response['data'] = result[0].body;
+            }
+            response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+            resolve(response);
+        }).catch(function(error) {
+            var response = {};
+            console.log('Problem with UserManagement.passwordChange', error);
+            response.statusCode = error.statusCode || 500;
+            response.errorMessage = {
+                error: 'Failed to passwordChange user' + error
+            };
+            reject(response);
+        });
+    });
+};
+
+UserManagement.delete = function(req) {
+    var self = this;
+    var username = req.params.username;
+    var domain = req.params.domain;
+    var api_server = req.query["api_server"];
+    var requestHeaders = {};
+    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,
+            constants.HTTP_HEADERS.content_type.data, {
+                'Authorization': req.session && req.session.authorization
+            });
+        rp({
+            url: url,
+            method: 'DELETE',
+            headers: requestHeaders,
+            forever: constants.FOREVER_ON,
+            rejectUnauthorized: false,
+        }, function(error, response, body) {
+            if (utils.validateResponse('UserManagement.DELETE', error, response, body, resolve, reject)) {
+                return resolve({
+                    statusCode: response.statusCode,
+                    data: JSON.stringify(response.body)
+                });
+            };
+        });
+    })
+}
+module.exports = UserManagement;
index c85eba6..7d22394 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -42,7 +42,6 @@ function addNavigation(plugin_name, routes) {
        if (!NAVIGATION[plugin_name]) {
                NAVIGATION[plugin_name] = {};
        }
-
        if (!NAVIGATION[plugin_name].routes) {
                NAVIGATION[plugin_name].routes = routes;
        } else {
@@ -69,6 +68,20 @@ function addLabel(plugin_name, label) {
        NAVIGATION[plugin_name].label = label || 'RW.UI Plugin';
 }
 
+function addAllow(plugin_name, allow) {
+       if (!NAVIGATION[plugin_name]) {
+               NAVIGATION[plugin_name] = {};
+       }
+       NAVIGATION[plugin_name].allow = allow || '*';
+}
+
+function addAdminFlag(plugin_name, admin_link) {
+       if (!NAVIGATION[plugin_name]) {
+               NAVIGATION[plugin_name] = {};
+       }
+       NAVIGATION[plugin_name].admin_link = admin_link || false;
+}
+
 function getNavigation() {
        return NAVIGATION;
 }
@@ -82,6 +95,8 @@ function onNavigationDiscovered(plugin_name, plugin) {
        addOrder(plugin_name, plugin.order);
        addPriority(plugin_name, plugin.priority);
        addLabel(plugin_name, plugin.name);
+       addAllow(plugin_name, plugin.allow);
+       addAdminFlag(plugin_name, plugin.admin_link);
 }
 
 function init() {
index b789ff0..61ebb65 100644 (file)
@@ -71,10 +71,10 @@ Router.get('/check-auth', function(req, res) {
 function checkAuth(uri, req){
     return new Promise(function(resolve, reject) {
         request({
-            uri: uri,
+            uri: utils.projectContextUrl(uri),
             method: 'GET',
             headers: _.extend({}, {
-                'Authorization': req.get('Authorization'),
+                'Authorization': req.session && req.session.authorization,
                 forever: CONSTANTS.FOREVER_ON,
                 rejectUnauthorized: false,
             })
index 82c7ec5..afffb20 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -37,8 +37,28 @@ Router.use(bodyParser.urlencoded({
     extended: true
 }));
 
-Router.get('/', cors(), function(req, res, next) {
-       res.redirect('/launchpad/?api_server=' + req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server + '&upload_server=' + req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname));
+Router.get('/login.html', cors(), function(req, res) {
+       res.render('login.html');
+       res.end();
+});
+
+Router.get('/', cors(), function(req, res) {
+       var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server);
+       if (req.session && req.session.loggedIn) {
+               console.log('Logged in. Redirect to launchpad');
+               if(req.params.referer) {
+                       res.redirect(req.params.referer);
+               }  else {
+                       if(req.session.isLCM) {
+                               res.redirect('/launchpad/?api_server=' + api_server + '&upload_server=' + req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname));
+                       } else {
+                               res.redirect('/user_management/?api_server=' + api_server + '&upload_server=' + req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname) + '#/user-profile');
+                       }
+               }
+       } else {
+               console.log('Redirect to login.html');
+               res.redirect('login.html?api_server=' + api_server + '&upload_server=' + req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname)  + '&referer=' + req.headers.referer);
+       }
 });
 
 Router.get('/nav', cors(), function(req, res) {
diff --git a/skyquake/framework/core/modules/routes/projectManagement.js b/skyquake/framework/core/modules/routes/projectManagement.js
new file mode 100644 (file)
index 0000000..c106f30
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ *
+ *   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.
+ *
+ */
+
+/**
+ * inactivity routes module. Provides a RESTful API for this
+ * skyquake instance's inactivity state.
+ * @module framework/core/modules/routes/inactivity
+ * @author Laurence Maultsby <laurence.maultsby@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+var ProjectManagementAPI = require('../api/projectManagementAPI.js');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+    extended: true
+}));
+
+Router.get('/project', cors(), function(req, res) {
+    ProjectManagementAPI.get(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+Router.post('/project', cors(), function(req, res) {
+    ProjectManagementAPI.create(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+Router.put('/project', cors(), function(req, res) {
+    ProjectManagementAPI.update(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+Router.delete('/project/:projectname', cors(), function(req, res) {
+    ProjectManagementAPI.delete(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+
+Router.put('/platform', cors(), function(req, res) {
+    ProjectManagementAPI.updatePlatform(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+
+Router.get('/platform', cors(), function(req, res) {
+    ProjectManagementAPI.getPlatform(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+module.exports = Router;
+
+
+
diff --git a/skyquake/framework/core/modules/routes/sessions.js b/skyquake/framework/core/modules/routes/sessions.js
new file mode 100644 (file)
index 0000000..bd2179b
--- /dev/null
@@ -0,0 +1,62 @@
+
+/*
+ *
+ *   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.
+ *
+ */
+
+/**
+ * Node sessions routes module.
+ * Provides a RESTful API to manage sessions.
+ * @module framework/core/modules/routes/sessions
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var sessionsAPI = require('../api/sessions');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+var CONSTANTS = require('../../api_utils/constants.js');
+var request = require('request');
+var _ = require('lodash');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+    extended: true
+}));
+
+Router.post('/session', cors(), function(req, res) {
+    sessionsAPI.create(req, res).then(function(data) {
+        utils.sendSuccessResponse(data, res);
+       });
+});
+
+// For project switcher UI
+Router.put('/session/:projectId', cors(), function(req, res) {
+    sessionsAPI.addProjectToSession(req, res).then(function(data) {
+        utils.sendSuccessResponse(data, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+
+Router.delete('/session', cors(), function(req, res) {
+    sessionsAPI.delete(req, res);
+});
+
+
+module.exports = Router;
diff --git a/skyquake/framework/core/modules/routes/userManagement.js b/skyquake/framework/core/modules/routes/userManagement.js
new file mode 100644 (file)
index 0000000..22a2d74
--- /dev/null
@@ -0,0 +1,85 @@
+
+/*
+ *
+ *   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.
+ *
+ */
+
+/**
+ * inactivity routes module. Provides a RESTful API for this
+ * skyquake instance's inactivity state.
+ * @module framework/core/modules/routes/inactivity
+ * @author Laurence Maultsby <laurence.maultsby@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+var UserManagementAPI = require('../api/userManagementAPI.js');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+    extended: true
+}));
+
+Router.get('/user', cors(), function(req, res) {
+    UserManagementAPI.get(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        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.get('/user-data/:userId/:domain?', cors(), function(req, res) {
+    UserManagementAPI.getUserInfo(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);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+Router.put('/user', cors(), function(req, res) {
+    UserManagementAPI.update(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+Router.delete('/user/:username/:domain', cors(), function(req, res) {
+    UserManagementAPI.delete(req).then(function(response) {
+        utils.sendSuccessResponse(response, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+
+module.exports = Router;
+
+
+
diff --git a/skyquake/framework/core/views/login.html b/skyquake/framework/core/views/login.html
new file mode 100644 (file)
index 0000000..404b21e
--- /dev/null
@@ -0,0 +1,115 @@
+<html>
+<head>
+<style>
+    html, body {
+        background: #f1f1f1;
+    }
+
+    #loginForm {
+        display: flex;
+        flex-direction: column;
+        text-align: center;
+        align-items: center;
+    }
+
+    #loginForm .logo {
+        background: url('/img/svg/osm-logo_color_rgb.svg') no-repeat center;
+        background-size: contain;
+        width: 154px;
+        height: 102px;
+        margin-left: auto;
+        margin-right: auto;
+        margin-top: 150px;
+        margin-bottom: 20px;
+    }
+
+    #loginForm input {
+        width: 550px;
+        height: 65px;
+        min-width: auto;
+        margin-bottom: 40px;
+        box-shadow: inset 0 1px 2px rgba(0, 0, 0, .39), 0 -1px 1px #fff, 0 1px 0 #fff;
+        font-size: 20px;
+        padding-left: 25px;
+    }
+
+    #loginForm #submit {
+        display: inline-block;
+        -webkit-box-shadow: 4px 4px 1px 0 #d9d9d9;
+        -moz-box-shadow: 4px 4px 1px 0 #d9d9d9;
+        box-shadow: 4px 4px 1px 0 #d9d9d9;
+        background-color: #333;
+        color: #fff;
+        text-decoration: none;
+    }
+
+    #loginForm .title {
+        margin-bottom: 40px;
+        font-size: 1.625rem;
+        font-weight: 400;
+        text-decoration: none;
+        text-transform: uppercase;
+        font-family: roboto-thin, Helvetica, Arial, sans-serif;
+    }
+
+</style>
+<title>Login Page</title>
+<script src='/jquery'></script>
+<script>
+
+function getSearchParams(url) {
+    var a = document.createElement('a');
+    a.href = url;
+    var params = {};
+    var items = a.search.replace('?', '').split('&');
+    for (var i = 0; i < items.length; i++) {
+      if (items[i].length > 0) {
+        var key_value = items[i].split('=');
+        params[key_value[0]] = key_value[1];
+      }
+    }
+    return params;
+}
+
+$(document).ready(function() {
+    var username;
+    var pass;
+    var api_server = getSearchParams(window.location).api_server;
+    $('#submit').click(function() {
+        username=$('#username').val();
+        pass=$('#password').val();
+        /*
+        * Perform some validation here.
+        */
+        $.ajax({
+            url: '/session?api_server=' + api_server,
+            type: 'POST',
+            data: {
+                username: username,
+                password: pass
+            },
+            success: function(data) {
+                window.location.href='/?api_server=' + api_server;
+            }
+        });
+    });
+
+    $('#loginForm').on('keyup', function(e) {
+        if (e.keyCode == 13) {
+            $('#submit').click();
+        }
+    });
+});
+
+</script>
+</head>
+<body>
+    <form id='loginForm' autocomplete='on'>
+        <div class='logo'> </div>
+        <h1 class='title'>Launchpad Login</h1>
+        <input type='text' size='40' placeholder='Username' id='username'><br />
+        <input type='password' size='40' placeholder='Password' id='password'><br />
+        <input type='button' value='Submit' id='submit'>
+    </form>
+</body>
+</html>
index 9378984..24a4475 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,7 +31,7 @@ $body-color:$lightest-gray;
 
 $error-red:#FF5F5F;
 
-//PC
+/*PC*/
 
 $black: #000;
 $gray-lightest: #f1f1f1;
@@ -42,9 +42,9 @@ $gray-dark: #999;
 $gray-darker: #666;
 $gray-darkest: #333;
 $white: #FFF;
-//
-// Brand Colors
-//
+/**/
+/* Brand Colors*/
+/**/
 $brand-blue-light: #30baef;
 $brand-blue: #00acee;
 $brand-blue-dark: #147ca3;
@@ -66,11 +66,11 @@ $neutral-light-3: hsl(360, 100%, 50%);
 $neutral-light-4: hsl(360, 100%, 50%);
 $neutral-light-5: hsl(360, 100%, 50%);
 
-$neutral-dark-1: hsl(360, 100%, 50%);
-$neutral-dark-2: hsl(360, 100%, 50%);
-$neutral-dark-3: hsl(360, 100%, 50%);
-$neutral-dark-4: hsl(360, 100%, 50%);
-$neutral-dark-5: hsl(360, 100%, 50%);
+$neutral-dark-1: hsl(0, 0%, 63.7%);
+$neutral-dark-2: hsl(0, 0%, 56.7%);
+$neutral-dark-3: hsl(0, 0%, 49.7%);
+$neutral-dark-4: hsl(0, 0%, 42.7%);
+$neutral-dark-5: hsl(0, 0%, 35.7%);
 $netral-black: hsl(0, 100%, 0%);
 
 
diff --git a/skyquake/framework/utils/roleConstants.js b/skyquake/framework/utils/roleConstants.js
new file mode 100644 (file)
index 0000000..96c4bf3
--- /dev/null
@@ -0,0 +1,20 @@
+var c = {};
+
+c.PLATFORM = {
+  OPER: "rw-rbac-platform:platform-oper",
+  ADMIN: "rw-rbac-platform:platform-admin",
+  SUPER: "rw-rbac-platform:super-admin"
+}
+
+c.PROJECT = {
+    CATALOG_OPER: "rw-project-mano:catalog-oper",
+    CATALOG_ADMIN: "rw-project-mano:catalog-admin",
+    LCM_OPER: "rw-project-mano:lcm-oper",
+    LCM_ADMIN: "rw-project-mano:lcm-admin",
+    ACCOUNT_OPER: "rw-project-mano:account-oper",
+    ACCOUNT_ADMIN: "rw-project-mano:account-admin",
+    PROJECT_ADMIN: "rw-project:project-admin",
+    PROJECT_OPER: "rw-project:project-oper",
+}
+
+module.exports = c;
index fc6b592..ed1f113 100644 (file)
@@ -26,6 +26,7 @@ var SockJS = require('sockjs-client');
 var Utils = {};
 
 Utils.DescriptorModelMeta = null;
+// Utils.DescriptorModelMeta = require('./../../plugins/composer/src/src/libraries/model/DescriptorModelMeta.json');
 
 var INACTIVITY_TIMEOUT = 600000;
 
@@ -129,8 +130,9 @@ Utils.getDescriptorModelMeta = function() {
 }
 
 Utils.addAuthorizationStub = function(xhr) {
-    var Auth = window.sessionStorage.getItem("auth");
-    xhr.setRequestHeader('Authorization', 'Basic ' + Auth);
+    // NO-OP now that we are dealing with it on the server
+    // var Auth = window.sessionStorage.getItem("auth");
+    // xhr.setRequestHeader('Authorization', 'Basic ' + Auth);
 };
 
 Utils.getByteDataWithUnitPrefix = function(number, precision) {
@@ -208,10 +210,22 @@ Utils.clearAuthentication = function(callback) {
     window.sessionStorage.removeItem("auth");
     AuthActions.notAuthenticated();
     window.sessionStorage.setItem("locationRefHash", window.location.hash);
+    $.ajax({
+        url: '//' + window.location.hostname + ':' + window.location.port + '/session?api_server=' + API_SERVER,
+        type: 'DELETE',
+        success: function(data) {
+            console.log('User logged out');
+        },
+        error: function(data) {
+            console.log('Problem logging user out');
+        }
+    });
+
+
     if (callback) {
         callback();
     } else {
-        window.location.hash = Utils.loginHash;
+        window.location.replace(window.location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/?api_server=' + API_SERVER);
     }
 }
 Utils.isNotAuthenticated = function(windowLocation, callback) {
index c972e14..043a769 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -62,15 +62,18 @@ button{
 ############################################################################ */
 
 .SqButton {
-  align-items: center;
+  -ms-flex-align: center;
+      align-items: center;
   border-style: solid;
   border-radius: 3px;
   border-width: 0px;
   cursor: pointer;
+  display: -ms-inline-flexbox;
   display: inline-flex;
   font-size: 1rem;
   height: 50px;
-  justify-content: center;
+  -ms-flex-pack: center;
+      justify-content: center;
   margin: 0 10px;
   outline: none;
   padding: 0 15px;
@@ -107,8 +110,9 @@ button{
 
   /* Focus */
   &:focus {
-    // box-shadow: $focus-shadow;
-    border: 1px solid red;
+    /* box-shadow: $focus-shadow;*/
+    border: 1px solid;
+    border-color: darken($normalHoverBackground, 10%);
   }
 
   /* SIZES
@@ -256,11 +260,14 @@ button{
       fill: $primaryForeground;
     }
   }
-
-
 }
 
-
+.sqButtonGroup {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-pack: center;
+      justify-content: center;
+}
 
 
 
index ae93128..8d0ec77 100644 (file)
@@ -36,17 +36,35 @@ export default class SqButton extends React.Component {
             Class += " is-disabled";
         }
         return (
-                <div style={{display: 'flex'}}>
+        <div style={{display: 'flex'}} onClick={this.props.onClick}>
             <div className={Class} tabIndex="0">
-            {svgHTML}
-              <div className="SqButton-content">{label}</div>
+                {svgHTML}
+                <div className="SqButton-content">{label}</div>
             </div>
+        </div>
+        )
+    }
+}
+
+export class ButtonGroup extends React.Component {
+    render() {
+        let className = "sqButtonGroup";
+        if (this.props.className) {
+            className = `${className} ${this.props.className}`
+        }
+        return (
+            <div className={className} style={this.props.style}>
+                {this.props.children}
             </div>
         )
     }
 }
 
+
 SqButton.defaultProps = {
+    onClick: function(e) {
+        console.log('Clicked')
+    },
     icon: false,
     primary: false,
     disabled: false,
index f38719a..1831197 100644 (file)
@@ -30,351 +30,3 @@ export default {
   Bullet: require('./bullet/bullet.js')
 };
 
-// require('../../assets/js/n3-line-chart.js');
-// var Gauge = require('../../assets/js/gauge-modified.js');
-// var bulletController = function($scope, $element) {
-//     this.$element = $element;
-//     this.vertical = false;
-//     this.value = 0;
-//     this.min = 0;
-//     this.max = 100;
-//     //this.range = this.max - this.min;
-//     //this.percent = (this.value - this.min) / this.range;
-//     this.displayValue = this.value;
-//     this.isPercent = (this.units == '')? true:false;
-//     this.bulletColor = "#6BB814";
-//     this.fontsize = 28;
-//     this.radius = 4;
-//     this.containerMarginX = 0;
-//     this.containerMarginY = 0;
-//     this.textMarginX = 5;
-//     this.textMarginY = 42;
-//     this.bulletMargin = 0;
-//     this.width = 512;
-//     this.height = 64;
-//     this.markerX = -100; // puts it off screen unless set
-//     var self = this;
-//     if (this.isPercent) {
-//         this.displayValue + "%";
-//     }
-//     $scope.$watch(
-//       function() {
-//         return self.value;
-//       },
-//       function() {
-//         self.valueChanged();
-//       }
-//     );
-
-//   }
-
-//   bulletController.prototype = {
-
-//     valueChanged: function() {
-//       var range = this.max - this.min;
-//       var normalizedValue = (this.value - this.min) / range;
-//       if (this.isPercent) {
-//         this.displayValue = String(Math.round(normalizedValue * 100)) + "%";
-//       } else {
-//         this.displayValue = this.value;
-//       }
-//       // All versions of IE as of Jan 2015 does not support inline CSS transforms on SVG
-//       if (platform.name == 'IE') {
-//         this.bulletWidth = Math.round(100 * normalizedValue) + '%';
-//       } else {
-//         this.bulletWidth = this.width - (2 * this.containerMarginX);
-//         var transform = 'scaleX(' + normalizedValue + ')';
-//         var bullet = $(this.$element).find('.bullet2');
-//         bullet.css('transform', transform);
-//         bullet.css('-webkit-transform', transform);
-//       }
-//     },
-
-//     markerChanged: function() {
-//       var range = this.max - this.min;
-//       var w = this.width - (2 * this.containerMarginX);
-//       this.markerX = this.containerMarginX + ((this.marker - this.min) / range ) * w;
-//       this.markerY1 = 7;
-//       this.markerY2 = this.width - 7;
-//     }
-//   }
-
-// angular.module('components', ['n3-line-chart'])
-//     .directive('rwBullet', function() {
-//       return {
-//         restrict : 'E',
-//         templateUrl: 'modules/views/rw.bullet.tmpl.html',
-//         bindToController: true,
-//         controllerAs: 'bullet',
-//         controller: bulletController,
-//         replace: true,
-//         scope: {
-//           min : '@?',
-//           max : '@?',
-//           value : '@',
-//           marker: '@?',
-//           units: '@?',
-//           bulletColor: '@?',
-//           label: '@?'
-//         }
-//       };
-//     })
-//     .directive('rwSlider', function() {
-//       var controller = function($scope, $element, $timeout) {
-//         // Q: is there a way to force attributes to be ints?
-//         $scope.min = $scope.min || "0";
-//         $scope.max = $scope.max || "100";
-//         $scope.step = $scope.step || "1";
-//         $scope.height = $scope.height || "30";
-//         $scope.orientation = $scope.orientation || 'horizontal';
-//         $scope.tooltipInvert = $scope.tooltipInvert || false;
-//         $scope.percent = $scope.percent || false;
-//         $scope.kvalue = $scope.kvalue || false;
-//         $scope.direction = $scope.direction || "ltr";
-//         $($element).noUiSlider({
-//           start: parseInt($scope.value),
-//           step: parseInt($scope.step),
-//           orientation: $scope.orientation,
-//           range: {
-//             min: parseInt($scope.min),
-//             max: parseInt($scope.max)
-//           },
-//           direction: $scope.direction
-//         });
-//         //$(".no-Ui-target").Link('upper').to('-inline-<div class="tooltip"></div>')
-//         var onSlide = function(e, value) {
-//           $timeout(function(){
-//             $scope.value = value;
-//           })
-
-//         };
-//         $($element).on({
-//           change: onSlide,
-//           slide: onSlide,
-//           set: $scope.onSet({value: $scope.value})
-//         });
-//         var val = String(Math.round($scope.value));
-//         if ($scope.percent) {
-//             val += "%"
-//         } else if ($scope.kvalue) {
-//             val += "k"
-//         }
-//         $($element).height($scope.height);
-//         if ($scope.tooltipInvert) {
-//             $($element).find('.noUi-handle').append("<div class='tooltip' style='position:relative;right:20px'>" + val + "</div>");
-//         } else {
-//             $($element).find('.noUi-handle').append("<div class='tooltip' style='position:relative;left:-20px'>" + val + "</div>");
-//         }
-//         $scope.$watch('value', function(value) {
-//         var val = String(Math.round($scope.value));
-//         if ($scope.percent) {
-//             val += "%"
-//         } else if($scope.kvalue) {
-//             val += "k"
-//         }
-//           $($element).val(value);
-//           $($element).find('.tooltip').html(val);
-//         if ($scope.tooltipInvert) {
-//             $($element).find('.tooltip').css('right', $($element).find('.tooltip').innerWidth() * -1);
-//         } else {
-//             $($element).find('.tooltip').css('left', $($element).find('.tooltip').innerWidth() * -1);
-//         }
-//         });
-//       };
-
-//       return {
-//         restrict : 'E',
-//         template: '<div></div>',
-//         controller : controller,
-//         replace: true,
-//         scope: {
-//           min : '@',
-//           max : '@',
-//           width: '@',
-//           height: '@',
-//           step : '@',
-//           orientation : '@',
-//           tooltipInvert: '@',
-//           percent: '@',
-//           kvalue: '@?',
-//           onSet:'&?',
-//           direction: '@?',
-//           value:'=?'
-//         }
-//       };
-//     })
-// .directive('rwGauge', function() {
-//     return {
-//         restrict: 'AE',
-//         template: '<canvas class="rwgauge" style="width:100%;height:100%;max-width:{{width}}px;max-height:240px;"></canvas>',
-//         replace: true,
-//         scope: {
-//             min: '@?',
-//             max: '@?',
-//             size: '@?',
-//             color: '@?',
-//             value: '@?',
-//             resize: '@?',
-//             isAggregate: '@?',
-//             units: '@?',
-//             valueFormat: '=?',
-//             width: '@?'
-//         },
-//         bindToController: true,
-//         controllerAs: 'gauge',
-//         controller: function($scope, $element) {
-//             var self = this;
-//             this.gauge = null;
-//             this.min = this.min || 0;
-//             this.max = this.max || 100;
-//             this.nSteps = 14;
-//             this.size = this.size || 300;
-//             this.units = this.units || '';
-//             $scope.width = this.width || 240;
-//             this.color = this.color || 'hsla(212, 57%, 50%, 1)';
-//             if (!this.valueFormat) {
-//                 if (this.max > 1000 || this.value) {
-//                     self.valueFormat = {
-//                         "int": 1,
-//                         "dec": 0
-//                     };
-//                 } else {
-//                     self.valueFormat = {
-//                         "int": 1,
-//                         "dec": 2
-//                     };
-//                 }
-//             }
-//             this.isAggregate = this.isAggregate || false;
-//             this.resize = this.resize || false;
-//             if (this.format == 'percent') {
-//                 self.valueFormat = {
-//                     "int": 3,
-//                     "dec": 0
-//                 };
-//             }
-//             $scope.$watch(function() {
-//                 return self.max;
-//             }, function(n, o) {
-//                 if(n !== o) {
-//                     renderGauge();
-//                 }
-//             });
-//             $scope.$watch(function() {
-//                 return self.valueFormat;
-//             }, function(n, o) {
-//                 if(n != 0) {
-//                     renderGauge();
-//                 }
-//             });
-//             $scope.$watch(function() {
-//                 return self.value;
-//             }, function() {
-//                 if (self.gauge) {
-//                     // w/o rounding gauge will unexplainably thrash round.
-//                     self.valueFormat = determineValueFormat(self.value);
-//                     self.gauge.setValue(Math.ceil(self.value * 100) / 100);
-//                     //self.gauge.setValue(Math.round(self.value));
-//                 }
-//             });
-//             angular.element($element).ready(function() {
-//                 console.log('rendering')
-//                 renderGauge();
-//             })
-//             window.testme = renderGauge;
-//             function determineValueFormat(value) {
-
-//                     if (value > 999 || self.units == "%") {
-//                         return {
-//                             "int": 1,
-//                             "dec": 0
-//                         }
-//                     }
-
-//                     return {
-//                         "int": 1,
-//                         "dec": 2
-//                     }
-//                 }
-//             function renderGauge(calcWidth) {
-//                 if (self.max == self.min) {
-//                     self.max = 14;
-//                 }
-//                 var range = self.max - self.min;
-//                 var step = Math.round(range / self.nSteps);
-//                 var majorTicks = [];
-//                 for (var i = 0; i <= self.nSteps; i++) {
-//                     majorTicks.push(self.min + (i * step));
-//                 };
-//                 var redLine = self.min + (range * 0.9);
-//                 var config = {
-//                     isAggregate: self.isAggregate,
-//                     renderTo: angular.element($element)[0],
-//                     width: calcWidth || self.size,
-//                     height: calcWidth || self.size,
-//                     glow: false,
-//                     units: self.units,
-//                     title: false,
-//                     minValue: self.min,
-//                     maxValue: self.max,
-//                     majorTicks: majorTicks,
-//                     valueFormat: determineValueFormat(self.value),
-//                     minorTicks: 0,
-//                     strokeTicks: false,
-//                     highlights: [],
-//                     colors: {
-//                         plate: 'rgba(0,0,0,0)',
-//                         majorTicks: 'rgba(15, 123, 182, .84)',
-//                         minorTicks: '#ccc',
-//                         title: 'rgba(50,50,50,100)',
-//                         units: 'rgba(50,50,50,100)',
-//                         numbers: '#fff',
-//                         needle: {
-//                             start: 'rgba(255, 255, 255, 1)',
-//                             end: 'rgba(255, 255, 255, 1)'
-//                         }
-//                     }
-//                 };
-//                 var min = config.minValue;
-//                 var max = config.maxValue;
-//                 var N = 1000;
-//                 var increment = (max - min) / N;
-//                 for (i = 0; i < N; i++) {
-//                     var temp_color = 'rgb(0, 172, 238)';
-//                     if (i > 0.5714 * N && i <= 0.6428 * N) {
-//                         temp_color = 'rgb(0,157,217)';
-//                     } else if (i >= 0.6428 * N && i < 0.7142 * N) {
-//                         temp_color = 'rgb(0,142,196)';
-//                     } else if (i >= 0.7142 * N && i < 0.7857 * N) {
-//                         temp_color = 'rgb(0,126,175)';
-//                     } else if (i >= 0.7857 * N && i < 0.8571 * N) {
-//                         temp_color = 'rgb(0,122,154)';
-//                     } else if (i >= 0.8571 * N && i < 0.9285 * N) {
-//                         temp_color = 'rgb(0,96,133)';
-//                     } else if (i >= 0.9285 * N) {
-//                         temp_color = 'rgb(0,80,112)';
-//                     }
-//                     config.highlights.push({
-//                         from: i * increment,
-//                         to: increment * (i + 2),
-//                         color: temp_color
-//                     })
-//                 }
-//                 var updateSize = _.debounce(function() {
-//                     config.maxValue = self.max;
-//                     var clientWidth = self.parentNode.parentNode.clientWidth / 2;
-//                     var calcWidth = (300 > clientWidth) ? clientWidth : 300;
-//                     self.gauge.config.width = self.gauge.config.height = calcWidth;
-//                     self.renderGauge(calcWidth);
-//                 }, 500);
-//                 if (self.resize) $(window).resize(updateSize)
-//                 if (self.gauge) {
-//                     self.gauge.updateConfig(config);
-//                 } else {
-//                     self.gauge = new Gauge(config);
-//                     self.gauge.draw();
-//                 }
-//             };
-//         },
-//     }
-// });
diff --git a/skyquake/framework/widgets/form_controls/formControls.jsx b/skyquake/framework/widgets/form_controls/formControls.jsx
new file mode 100644 (file)
index 0000000..e50fb7e
--- /dev/null
@@ -0,0 +1,113 @@
+import './formControls.jsx';
+
+import React from 'react'
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import imgAdd from '../../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg'
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import Input from 'widgets/form_controls/input.jsx';
+
+export 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: ''
+}
+
+/**
+ * AddItemFn:
+ */
+export 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}`)
+    }
+}
index 4a88435..1e29db7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,7 +15,7 @@
  *   limitations under the License.
  *
  */
-@import 'style/_colors.scss';
+@import '../../style/_colors.scss';
 
 .sqTextInput {
     display: -ms-flexbox;
@@ -32,7 +32,7 @@
         color:$darker-gray;
         text-transform:uppercase;
     }
-    input, .readonly, textarea {
+    input, textarea {
         height: 35px;
         box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff;
         font-size: 1rem;
@@ -49,6 +49,7 @@
     .readonly {
         line-height: 35px;
         box-shadow:none;
+        background:none !important;
     }
     textarea {
         -ms-flex-align: stretch;
         border:0px;
         height: 100%;
     }
+    &.checkbox {
+        -ms-flex-direction:row;
+            flex-direction:row;
+            -ms-flex-align:center;
+                align-items:center;
+        margin-bottom:0;
+            >span {
+                -ms-flex-order: 1;
+                    order: 1;
+                    padding-left:1rem;
+            }
+            >input {
+                -ms-flex-order: 0;
+                    order: 0;
+
+                    box-shadow:none;
+                    height:25px;
+            }
+    }
+}
+
+.sqCheckBox {
+    display:-ms-flexbox;
+    display:flex;
+    label {
+        display:-ms-flexbox;
+        display:flex;
+        -ms-flex-align: center;
+        align-items: center;
+        input {
+            box-shadow: none;
+            height: auto;
+            margin: 0 0.25rem;
+        }
+    }
+}
+
+.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 {
+
+    }
 }
diff --git a/skyquake/framework/widgets/form_controls/input.jsx b/skyquake/framework/widgets/form_controls/input.jsx
new file mode 100644 (file)
index 0000000..1563f29
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ *
+ *   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 './formControls.scss';
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import CircleSVG from '../../../node_modules/open-iconic/svg/media-record.svg'
+import React, {Component} from 'react';
+
+export default class Input extends Component {
+    render() {
+        let {label, value, defaultValue, ...props} = this.props;
+        let inputProperties = {
+            value: value
+        }
+        let isRequired;
+        let inputType;
+        let className = `sqTextInput ${props.className}`;
+
+        if(this.props.required) {
+           isRequired = <span className="required">*</span>
+        }
+        if (defaultValue) {
+            inputProperties.defaultValue = defaultValue;
+        }
+        if (props.pattern) {
+            inputProperties.pattern = props.pattern;
+        }
+        if(props.hasOwnProperty('type') && (props.type.toLowerCase() == 'checkbox')) {
+            inputProperties.checked = props.checked;
+            className = `${className} checkbox`;
+        }
+        if (value == undefined) {
+            value = defaultValue;
+        }
+        switch(props.type) {
+            case 'textarea':
+                inputType = <textarea key={props.key} {...inputProperties} value={value} onChange={props.onChange} />
+                break;
+            case 'select':
+                inputType = <SelectOption
+                                key={props.key}
+                                initial={props.initial}
+                                defaultValue={defaultValue}
+                                options={props.options}
+                                onChange={props.onChange}
+                            />
+                break;
+            case 'radiogroup':
+                inputType = buildRadioButtons(this.props);
+                break;
+            default:
+                inputType = <input key={props.key} type={props.type} {...inputProperties} onChange={props.onChange} placeholder={props.placeholder}/>;
+        }
+        let displayedValue;
+        if(value === null) {
+            displayedValue = null;
+        } else {
+            displayedValue = value.toString();
+        }
+        if( props.readonly && props.type == "checkbox" && props.checked ) {
+            displayedValue = <img src={CircleSVG} />
+        }
+        let html = (
+            <label className={className} style={props.style}>
+              <span> { label } {isRequired}</span>
+              {
+                !props.readonly ? inputType : <div className="readonly">{displayedValue}</div>
+              }
+
+            </label>
+        );
+        return html;
+    }
+}
+
+
+function buildRadioButtons(props) {
+    let className = 'sqCheckBox';
+    if (props.className) {
+        className = `${className} ${props.className}`;
+    }
+    return(
+       <div className={className}>
+            {
+                props.options.map((o,i) => {
+                    return (
+                        <label key={i}>
+                            {o.label}
+                            <input type="radio" checked={props.value == o.value} value={o.value} onChange={props.onChange} />
+                        </label>
+                    )
+                })
+            }
+       </div>
+
+    )
+}
+
+Input.defaultProps = {
+    onChange: function(e) {
+        console.log(e.target.value, e);
+        console.dir(e.target);
+    },
+    label: '',
+    defaultValue: null,
+    type: 'text',
+    readonly: false,
+    style:{},
+    className: ''
+
+}
+
index 41a8b13..397963e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,32 +28,65 @@ 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 value = JSON.stringify(op.value);
-      return <option key={i} value={JSON.stringify(op.value)}>{op.label}</option>
-    });
+    let options =  this.props.options && this.props.options.map(function(op, i) {
+    let value;
+    let label;
+    if(typeof(op) == 'object') {
+      value = JSON.stringify(op.value);
+      label = op.label;
+    } else {
+      value = op;
+      label = op;
+    }
+
+      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>
+        <label key={this.props.key} className={this.props.className}>
             {this.props.label}
-            <select className={this.props.className} onChange={this.handleOnChange} defaultValue={JSON.stringify(defaultValue)} >
-                {
-                 options
-                }
-            </select>
+            {
+              this.props.readonly ? defaultValue
+              : (
+                  <select
+                    className={this.props.className}
+                    onChange={this.handleOnChange}
+                    value={JSON.stringify(this.props.value)}
+                    defaultValue={JSON.stringify(defaultValue)}>
+                      {
+                       options
+                      }
+                  </select>
+                )
+            }
         </label>
     );
     return html;
   }
 }
 SelectOption.defaultProps = {
+  /**
+   * [options description]
+   * @type {Array} - Expects items to contain objects with the properties 'label' and 'value' which are both string types. Hint: JSON.stringify()
+   */
   options: [],
   onChange: function(e) {
+    console.log(e.target.value)
     console.dir(e)
   },
-  defaultValue: false,
+  readonly: false,
+  /**
+   *  Selected or default value
+​
+   * @type {[type]}
+   */
+  defaultValue: null,
+  /**
+   * True if first entry in dropdown should be blank
+   * @type {Boolean}
+   */
   initial: false,
   label: null
 }
index 03dfa9c..000dcf7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
 import './formControls.scss';
 
 import React, {Component} from 'react';
-
-export default class TextInput extends Component {
-    render() {
-        let {label, onChange, value, defaultValue, ...props} = this.props;
-        let inputProperties = {
-            value: value,
-            onChange: onChange
-        }
-        let isRequired;
-        let inputType;
-        if(this.props.required) {
-           isRequired = <span className="required">*</span>
-        }
-        if (defaultValue) {
-            inputProperties.defaultValue = defaultValue;
-        }
-        if (props.pattern) {
-            inputProperties.pattern = props.pattern;
-        }
-        if (value == undefined) {
-            value = defaultValue;
-        }
-        switch(props.type) {
-            case 'textarea':
-                inputType = <textarea {...inputProperties} value={value}/>
-
-                break;
-            default:
-                inputType = <input type={props.type} {...inputProperties} placeholder={props.placeholder}/>;
-        }
-        let html = (
-            <label className={"sqTextInput " + props.className} style={props.style}>
-              <span> { label } {isRequired}</span>
-              {
-                !props.readonly ? inputType : <div className="readonly">{value}</div>
-              }
-
-            </label>
-        );
-        return html;
+import Input from './input.jsx';
+class TextInput extends Input {
+    constructor(props) {
+        super(props);
+        console.warn('TextInput is deprecated. Use Input component instead')
     }
 }
-
-TextInput.defaultProps = {
-    onChange: function(e) {
-        console.log(e.target.value);
-    },
-    label: '',
-    defaultValue: undefined,
-    type: 'text',
-    readonly: false,
-    style:{}
-
-}
+export default TextInput;
 
index 5e1e717..6a2e56c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
  */
 
 header.header-app-component {
-    padding: 20px 0px;
+    padding: 10px 0px;
+    display:-ms-flexbox;
     display:flex;
-    flex-direction:column;
+    -ms-flex-direction:column;
+        flex-direction:column;
     .header-app-main {
+        display:-ms-flexbox;
         display:flex;
-        flex-direction:row;
-        justify-content:space-between;
-        align-items:center;
+        -ms-flex-direction:row;
+            flex-direction:row;
+        -ms-flex-pack:justify;
+            justify-content:space-between;
+        -ms-flex-align:center;
+            align-items:center;
     }
     h1 {
         /*background: url('../../style/img/header-logo.png') no-repeat;*/
@@ -39,15 +45,19 @@ header.header-app-component {
         font-size: 1.625rem;
         font-weight: 400;
         position:relative;
-        flex: 1 0 auto;
+        -ms-flex: 1 0 auto;
+            flex: 1 0 auto;
 
     }
     ul {
+            display:-ms-flexbox;
             display:flex;
         }
         li {
+            display:-ms-flexbox;
             display:flex;
-             flex:1 1 auto;
+             -ms-flex:1 1 auto;
+                 flex:1 1 auto;
              border-right:1px solid #e5e5e5;
              padding: 0 1rem;
             &:last-child {
@@ -55,12 +65,13 @@ header.header-app-component {
             }
             a {
                 cursor:pointer;
-                // padding: 0.125rem;
-                // border-bottom:1px solid black;
+                /* padding: 0.125rem;*/
+                /* border-bottom:1px solid black;*/
                 text-decoration:underline;
             }
         }
     .header-app-nav {
+        display:-ms-flexbox;
         display:flex;
         margin-left: 0.25rem;
         a,span {
@@ -81,8 +92,11 @@ header.header-app-component {
         }
     }
     nav {
+        display:-ms-flexbox;
         display:flex;
-        flex:0 1 auto;
-        align-items:center;
+        -ms-flex:0 1 auto;
+            flex:0 1 auto;
+        -ms-flex-align:center;
+            align-items:center;
     }
 }
index 4ef7c89..03877b0 100644 (file)
@@ -18,6 +18,7 @@
 import React, {Component} from 'react';
 import 'style/core.css';
 import './panel.scss';
+import circleXImage from '../../../node_modules/open-iconic/svg/circle-x.svg';
 export class Panel extends Component {
     constructor(props) {
         super(props)
@@ -28,8 +29,15 @@ export class Panel extends Component {
         let classRoot = className ? ' ' + className : ' ';
         let hasCorners = this.props['no-corners'];
         let titleTag = title ? <header className="skyquakePanel-title">{title}</header> : '';
+        let closeButton = (
+            <a onClick={self.props.hasCloseButton}
+              className={"close-btn"}>
+              <img src={circleXImage} title="Close card" />
+              </a>
+        );
         return (
             <section className={'skyquakePanel' + classRoot} style={props.style}>
+                {  self.props.hasCloseButton ? closeButton : null}
                 { !hasCorners ? <i className="corner-accent top left"></i> : null }
                 { !hasCorners ? <i className="corner-accent top right"></i> : null }
                 {titleTag}
@@ -51,13 +59,23 @@ Panel.defaultProps = {
 
 export class PanelWrapper extends Component {
     render() {
+        let wrapperClass = 'skyquakePanelWrapper';
+        let {className, column, style, ...props} = this.props;
+        if(className) {
+            wrapperClass = `${wrapperClass} ${className}`
+        }
+        if(column) {
+            style.flexDirection = 'column';
+        }
         return (
-        <div className={'skyquakePanelWrapper ' + this.props.className} style={this.props.style}>
+        <div className={wrapperClass} style={style} {...props}>
             {this.props.children}
         </div>)
     }
 }
-
+PanelWrapper.defaultProps = {
+    style: {}
+}
 export default Panel;
 
 
index 2a31b1c..75dc8ac 100644 (file)
             width:100%;
             height:100%;
         }
+    .close-btn {
+        cursor:pointer;
+        position: absolute;
+        right: -0.5rem;
+        top: -0.5rem;
+        img {
+            width: 1rem;
+        }
+    }
 }
 
 .skyquakePanelWrapper.column {
+    -ms-flex-direction:column;
+            flex-direction:column;
+        display:-ms-flexbox;
+        display:flex;
     .skyquakePanel-wrapper {
         height:auto;
     }
index a01f154..b2493a3 100644 (file)
@@ -1,4 +1,4 @@
-//import a reset
+/*import a reset*/
 @import '../../style/_colors.scss';
 html, body {
     height:100%;
@@ -7,113 +7,34 @@ html, body {
 
 
 .skyquakeApp {
+    display: -ms-flexbox;
     display: flex;
-    flex-direction: column;
+    -ms-flex-direction: column;
+        flex-direction: column;
     height: 100%;
     background: $gray-lightest;
     h1 {
         text-transform: uppercase;
     }
-    .active {
-        background-color: $brand-blue!important;
-        border-color: $brand-blue!important;
-        color: #fff!important
-    }
-    .skyquakeNav {
-        display: flex;
-        color:white;
-        background:black;
-        position:relative;
-        z-index: 10;
-        font-size:0.75rem;
-        .secondaryNav {
-            flex: 1 1 auto;
-            display: flex;
-            justify-content: flex-end;
-        }
-        .app {
-            position:relative;
-            h2 {
-                font-size:0.75rem;
-                border-right: 1px solid black;
-                display: flex;
-                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 {
         padding: 1rem 0 0;
         h1 {
-        // background: url('../../style/img/header-logo.png') no-repeat;
+        /* background: url('../../style/img/header-logo.png') no-repeat;*/
         background-size:contain;
         height: 51px;
         line-height: 51px;
         margin-left: 20px;
-        // padding-left: 100px;
+        /* padding-left: 100px;*/
         left: 0;
         font-size: 1.625rem;
         font-weight: 400;
         text-align:left;
         position:relative;
-        flex: 1 0 auto;
+        -ms-flex: 1 0 auto;
+            flex: 1 0 auto;
         }
     }
     .corner-accent {
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 d83c836..8b61f4b 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';
@@ -39,13 +39,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();
         });
 
@@ -84,7 +92,7 @@ export default class skyquakeContainer extends React.Component {
     render() {
         const {displayNotification, notificationMessage, displayScreenLoader, notificationType, ...state} = this.state;
         var html;
-
+        let nav = _.cloneDeep(this.state.nav);
         if (this.matchesLoginUrl()) {
             html = (
                 <AltContainer>
@@ -109,11 +117,14 @@ export default class skyquakeContainer extends React.Component {
                             timeout= {5000}
                         />
                         <ScreenLoader show={displayScreenLoader}/>
-                        <SkyquakeNav nav={this.state.nav}
-                            currentPlugin={this.state.currentPlugin}
-                            store={SkyquakeContainerStore} />
+                        <SkyquakeNav nav={nav}
+                            currentPlugin={this.state.user.currentPlugin}
+                            currentUser={this.state.user.userId}
+                            currentProject={this.state.user.projectId}
+                            store={SkyquakeContainerStore}
+                            projects={this.state.projects} />
                         <div className="titleBar">
-                            <h1>{this.state.currentPlugin + tag}</h1>
+                            <h1>{(this.state.nav.name ? this.state.nav.name.replace('_', ' ').replace('-', ' ') : this.state.currentPlugin && this.state.currentPlugin.replace('_', ' ').replace('-', ' ')) + tag}</h1>
                         </div>
                         <div className={"application " + routeName}>
                             {this.props.children}
@@ -130,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 773978b..ed97f0c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,5 +29,8 @@ export default Alt.generateActions(
     'hideNotification',
     //Screen Loader
     'showScreenLoader',
-    'hideScreenLoader'
+    'hideScreenLoader',
+    'openProjectSocketSuccess',
+    'getUserProfileSuccess',
+    'selectActiveProjectSuccess'
 );
index da7080f..8afaf83 100644 (file)
@@ -114,6 +114,84 @@ 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);
+                    });;
+                });
+            },
+            loading: Alt.actions.global.showScreenLoader,
+            success: SkyquakeContainerActions.getUserProfileSuccess
+        }
+    },
+
+    selectActiveProject() {
+        return {
+            remote: function(state, event) {
+                let projectId;
+                try {
+                    projectId = JSON.parse(JSON.parse(event.currentTarget.value));
+                } catch(e) {
+                    console.log('Something went wrong in the selectActiveProject source function', e);
+                }
+
+                return new Promise(function(resolve, reject) {
+                    $.ajax({
+                        url: `/session/${projectId}?api_server=${API_SERVER}`,
+                        type: 'PUT',
+                        beforeSend: Utils.addAuthorizationStub,
+                        success: function(data) {
+                            resolve(projectId);
+                        }
+                    }).fail(function(xhr) {
+                        //Authentication and the handling of fail states should be wrapped up into a connection class.
+                        Utils.checkAuthentication(xhr.status);
+                    });;
+                });
+            },
+            success: SkyquakeContainerActions.selectActiveProjectSuccess
+        }
     }
 }
 
index 56ebdda..87651e5 100644 (file)
@@ -20,7 +20,9 @@
 import Alt from './skyquakeAltInstance.js';
 import SkyquakeContainerSource from './skyquakeContainerSource.js';
 import SkyquakeContainerActions from './skyquakeContainerActions';
+let Utils = require('utils/utils.js');
 import _indexOf from 'lodash/indexOf';
+import _isEqual from 'lodash/isEqual';
 //Temporary, until api server is on same port as webserver
 import rw from 'utils/rw.js';
 
@@ -33,6 +35,8 @@ class SkyquakeContainerStore {
         this.nav = {};
         this.notifications = [];
         this.socket = null;
+        this.projects = [];
+        this.user = {};
         //Notification defaults
         this.notificationMessage = '';
         this.displayNotification = false;
@@ -162,6 +166,43 @@ 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();
+                });
+                if (!_isEqual(data.project, self.projects)) {
+                    let user = self.user;
+                    user.projects = data.project;
+                    self.setState({
+                        user: user,
+                        projects: data.project
+                    });
+                }
+            } catch(e) {
+                console.log('HIT an exception in openProjectSocketSuccess', e);
+            }
+        };
+    }
+    getUserProfileSuccess = (user) => {
+        this.alt.actions.global.hideScreenLoader.defer();
+        this.setState({user})
+    }
+    selectActiveProjectSuccess = (projectId) => {
+        let user = this.user;
+        user.projectId = projectId;
+        this.setState({user});
+        window.location.reload(true);
+    }
     //Notifications
     showNotification = (data) => {
         let state = {
@@ -173,7 +214,8 @@ class SkyquakeContainerStore {
         if(typeof(data) == 'string') {
 
         } else {
-            state.notificationMessage = data.msg;
+            if(!data) data = {};
+            state.notificationMessage = data.msg || 'Something wrong occurred. Check the network tab and console logs for more information.';
             if(data.type) {
                 state.notificationType = data.type;
             }
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeNav.jsx b/skyquake/framework/widgets/skyquake_container/skyquakeNav.jsx
deleted file mode 100644 (file)
index 8814a18..0000000
+++ /dev/null
@@ -1,227 +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
-import rw from '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..d9dff0b
--- /dev/null
@@ -0,0 +1,357 @@
+/*
+ *
+ *   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';
+import {isRBACValid, SkyquakeRBAC} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+
+//Temporary, until api server is on same port as webserver
+import rw from '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 SelectProject extends React.Component {
+    constructor(props) {
+        super(props);
+    }
+    selectProject(e) {
+        let value = JSON.parse(e.currentTarget.value);
+        console.log('selected project', value)
+    }
+    render() {
+        let props = this.props;
+        let currentValue = JSON.stringify(props.currentProject);
+        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">
+                {
+                    hasProjects ?  'Project:' : 'No Projects Assigned'
+                }
+                {
+                    hasProjects ?
+                    <SelectOption
+                        options={projects}
+                        value={currentValue}
+                        defaultValue={currentValue}
+                        onChange={props.onSelectProject}
+                        className="projectSelect" />
+                    : null
+                }
+            </div>
+        )
+    }
+}
+
+class UserNav extends React.Component {
+    constructor(props) {
+        super(props);
+    }
+    handleLogout() {
+        Utils.clearAuthentication();
+    }
+    selectProject(e) {
+        let value = JSON.parse(e.currentTarget.value)
+        console.log('selected project', value)
+    }
+    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>
+                    USER: {userProfileLink}
+                    <span className="oi" data-glyph="caret-bottom"></span>
+                </h2>
+                <ul className="menu">
+                    <li>
+                        <a onClick={this.handleLogout}>
+                            Logout
+                        </a>
+                    </li>
+                </ul>
+            </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: {}
+}
+skyquakeNav.contextTypes = {
+  userProfile: React.PropTypes.object
+};
+/**
+ * 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, label) {
+    let ref;
+    let route = link.route;
+    if(link.isExternal) {
+        ref = (
+            <a href={route}>{label || 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}>
+                {label || 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 adminNav = [];
+    let self = this;
+    const User = this.context.userProfile;
+    self.hasSubNav = {};
+    let secondaryNavHTML = (
+        <div className="secondaryNav" key="secondaryNav">
+            {secondaryNav}
+            <div className="app admin">
+                <h2>
+                    <a>
+                        ADMIN <span className="oi" data-glyph="caret-bottom"></span>
+                    </a>
+                </h2>
+                <ul className="menu">
+                    {
+                        adminNav
+                    }
+                </ul>
+            </div>
+            <SelectProject
+                onSelectProject={props.store.selectActiveProject}
+                projects={props.projects}
+                currentProject={props.currentProject} />
+            <UserNav
+                currentUser={props.currentUser}
+                nav={nav}  />
+        </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
+            });
+            let shouldAllow = nav[k].allow || ['*'];
+            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;
+            if(nav[k].admin_link) {
+
+                if (isRBACValid(User, shouldAllow) ){
+                    adminNav.push((
+                        <li key={nav[k].name}>
+                            {dashboardLink}
+                        </li>
+                    ))
+                }
+            } else {
+                if (isRBACValid(User, shouldAllow) ){
+                    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..6d2e581
--- /dev/null
@@ -0,0 +1,115 @@
+@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;
+        padding-right:1rem;
+        .secondaryNav {
+            -ms-flex: 1 1 auto;
+                flex: 1 1 auto;
+            display: -ms-flexbox;
+            display: flex;
+            -ms-flex-pack: end;
+                justify-content: flex-end;
+        }
+        .app {
+            display: -ms-flexbox;
+            display: block;
+            position:relative;
+            margin: auto 0.5rem;
+            min-width: 140px;
+            h2 {
+                font-size:0.75rem;
+                border-right: 1px solid black;
+                display: -ms-flexbox;
+                display: flex;
+                -ms-flex-pack: start;
+                -ms-flex-pack: start;
+                justify-content: flex-start;
+                -ms-flex-align: center;
+                align-items: center;
+                .oi {
+                    padding-right: 0.5rem;
+                }
+            }
+            .menu {
+                position:absolute;
+                display:none;
+                z-index:2;
+                width: 100%;
+                li {
+                    text-align:left;
+                }
+            }
+            &: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;
+            text-align:left;
+            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;
+            text-align: left;
+            border-left:1px solid white;
+            .projectSelect {
+                padding: 0 0.5rem;
+                font-size: 1rem;
+                /* min-width: 75%;*/
+                height: 25px;
+            }
+        }
+    }
diff --git a/skyquake/framework/widgets/skyquake_rbac/skyquakeRBAC.jsx b/skyquake/framework/widgets/skyquake_rbac/skyquakeRBAC.jsx
new file mode 100644 (file)
index 0000000..0245e23
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ *
+ *   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 ROLES from 'utils/roleConstants.js';
+const PLATFORM = ROLES.PLATFORM;
+
+export function isRBACValid(User, allow){
+  const UserData = User.data;
+  if(UserData) {
+      const PlatformRole = UserData.platform.role;
+      const isPlatformSuper = PlatformRole[PLATFORM.SUPER];
+      const isPlatformAdmin = PlatformRole[PLATFORM.ADMIN];
+      const isPlatformOper = PlatformRole[PLATFORM.OPER];
+      const hasRoleAccess =  checkForRoleAccess(UserData.project[User.projectId], PlatformRole, allow)//false//(this.props.roles.indexOf(userProfile.projectRole) > -1)
+      if (isPlatformSuper) {
+        return true;
+      } else {
+        if (hasRoleAccess) {
+          return true;
+        }
+      }
+    }
+  return false;
+}
+
+export default class SkyquakeRBAC extends React.Component {
+    constructor(props, context) {
+        super(props);
+    }
+    render() {
+      const User = this.context.userProfile;
+      const UserData = User.data;
+      let HTML = null;
+      // If user object has platform property then it has been populated by the back end.
+      if(isRBACValid(User, this.props.allow)) {
+        HTML = this.props.children;
+      }
+      return (<div className={this.props.className} style={this.props.style}>{HTML}</div>)
+    }
+}
+SkyquakeRBAC.defaultProps = {
+  allow: []
+}
+SkyquakeRBAC.contextTypes = {
+  userProfile: React.PropTypes.object
+}
+
+function checkForRoleAccess(project, PlatformRole, allow) {
+    if (allow.indexOf('*') > -1) return true;
+    for (let i = 0; i<allow.length; i++) {
+      if((project && project.role[allow[i]] )|| PlatformRole[allow[i]]) {
+        return true
+      }
+    }
+    return false;
+  }
+
+
+
+// export default function(Component) {
+//   class SkyquakeRBAC extends React.Component {
+//     constructor(props, context) {
+//         super(props);
+//             }
+//     render(props) {
+//       console.log(this.context.userProfile)
+//       const User = this.context.userProfile.data;
+//       // If user object has platform property then it has been populated by the back end.
+//       if(User) {
+//         const PlatformRole = User.platform.role;
+//         const HTML = <Component {...this.props} router={this.router} actions={this.actions} flux={this.context.flux}/>;
+//         const isPlatformSuper = PlatformRole[PLATFORM.SUPER];
+//         const isPlatformAdmin = PlatformRole[PLATFORM.ADMIN];
+//         const isPlatformOper = PlatformRole[PLATFORM.OPER];
+//         const hasRoleAccess =  false//(this.props.roles.indexOf(userProfile.projectRole) > -1)
+//         if (isPlatformSuper || isPlatformOper || isPlatformAdmin) {
+//           return HTML
+//         } else {
+//           if (hasRoleAccess) {
+//             return HTML
+//           } else {
+//             return null;
+//           }
+//         }
+//       }
+//       else {
+//         return null;
+
+//       }
+//     }
+//   }
+//   SkyquakeRBAC.defaultProps = {
+
+//   }
+//   SkyquakeRBAC.contextTypes = {
+//     userProfile: React.PropTypes.object,
+//     allowedRoles: []
+//   };
+//   return SkyquakeRBAC;
+// }
index 8578562..60d22db 100644 (file)
@@ -12,6 +12,7 @@
   "dependencies": {
     "alt": "^0.18.3",
     "alt-container": "^1.0.2",
+    "base-64": "^0.1.0",
     "bluebird": "^3.4.1",
     "body-parser": "^1.14.2",
     "cors": "^2.7.1",
index acfe863..60a8102 100644 (file)
@@ -1,4 +1,4 @@
-# 
+#
 #   Copyright 2016 RIFT.IO Inc
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,7 +15,7 @@
 #
 # Author(s): Kiran Kashalkar
 # Creation Date: 08/18/2015
-# 
+#
 
 ##
 # DEPENDENCY ALERT
@@ -44,6 +44,8 @@ set(
     composer
     config
     debug
+    project_management
+    user_management
 #    goodbyworld
 #    helloworld
     launchpad
index d77a43a..82cbafb 100644 (file)
@@ -32,7 +32,7 @@ About.getVCS = function(req) {
     var requestHeaders = {};
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
     request({
         url: utils.confdPort(api_server) + APIVersion + '/api/operational/vcs/info?deep',
@@ -64,7 +64,7 @@ About.getVersion = function(req) {
     var requestHeaders = {};
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
     request({
         url: utils.confdPort(api_server) + APIVersion + '/api/operational/version?deep',
@@ -116,7 +116,7 @@ About.uptime = function(req) {
     var requestHeaders = {};
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
     request({
         url: utils.confdPort(api_server) + APIVersion + '/api/operational/uptime/uptime',
index 1100c71..1885a9c 100644 (file)
@@ -4,6 +4,7 @@
     "dashboard": "./about.jsx",
     "order": 99,
     "priority":2,
+    "admin_link": true,
      "routes" : [{
         "label": "Dashboard",
         "route": "/",
index ec74f51..d26b566 100644 (file)
@@ -93,12 +93,12 @@ function getAccount(req) {
         _.extend(
             requestHeaders,
             id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }
         );
 
         request({
-                url: url + '?deep',
+                url: utils.projectContextUrl(req, url + '?deep'),
                 type: 'GET',
                 headers: requestHeaders,
                 forever: constants.FOREVER_ON,
@@ -160,10 +160,10 @@ function updateAccount(req) {
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
         request({
-            url: url,
+            url: utils.projectContextUrl(req, url),
             method: method,
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -194,10 +194,10 @@ function deleteAccount(req) {
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
         request({
-            url: url,
+            url: utils.projectContextUrl(req, url),
             method: 'DELETE',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -238,13 +238,17 @@ function refreshAccountConnectionStatus (req) {
     var headers = _.extend({},
         constants.HTTP_HEADERS.accept.data,
         constants.HTTP_HEADERS.content_type.data, {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         }
     );
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/' + rpcInfo[Type].rpc);
+    
+    jsonData['input'] = utils.addProjectContextToRPCPayload(req, uri, jsonData['input']);
+
     return new Promise(function(resolve, reject) {
 
         request({
-            uri: utils.confdPort(api_server) + '/api/operations/' + rpcInfo[Type].rpc,
+            uri: uri,
             method: 'POST',
             headers: headers,
             forever: constants.FOREVER_ON,
index 5475750..ade5861 100644 (file)
@@ -40,11 +40,11 @@ Cloud.get = function(req) {
       var requestHeaders = {};
       _.extend(requestHeaders,
         constants.HTTP_HEADERS.accept.collection, {
-          'Authorization': req.get('Authorization')
+          'Authorization': req.session && req.session.authorization
         });
 
       request({
-          url: utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account',
+          url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account'),
           type: 'GET',
           headers: requestHeaders,
           forever: constants.FOREVER_ON,
@@ -77,11 +77,11 @@ Cloud.get = function(req) {
       var requestHeaders = {};
       _.extend(requestHeaders,
         constants.HTTP_HEADERS.accept.data, {
-          'Authorization': req.get('Authorization')
+          'Authorization': req.session && req.session.authorization
         });
 
       request({
-          url: utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + id,
+          url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + id),
           type: 'GET',
           headers: requestHeaders,
           forever: constants.FOREVER_ON,
@@ -128,11 +128,11 @@ Cloud.create = function(req) {
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data,
       constants.HTTP_HEADERS.content_type.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
 
     request({
-      url: utils.confdPort(api_server) + APIVersion + '/api/config/cloud',
+      url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/cloud'),
       method: 'POST',
       headers: requestHeaders,
       forever: constants.FOREVER_ON,
@@ -167,11 +167,11 @@ Cloud.update = function(req) {
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data,
       constants.HTTP_HEADERS.content_type.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
 
     request({
-      url: utils.confdPort(api_server) + APIVersion + '/api/config/cloud/account/' + id,
+      url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/cloud/account/' + id),
       method: 'PUT',
       headers: requestHeaders,
       forever: constants.FOREVER_ON,
@@ -209,10 +209,10 @@ Cloud.delete = function(req) {
     var requestHeaders = {};
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
     request({
-      url: utils.confdPort(api_server) + APIVersion + '/api/config/cloud/account/' + id,
+      url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/cloud/account/' + id),
       method: 'DELETE',
       headers: requestHeaders,
       forever: constants.FOREVER_ON,
@@ -237,11 +237,11 @@ Cloud.getResources = function(req) {
     var requestHeaders = {};
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
 
     request({
-        url: utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + cloudAccount + '/resources?deep',
+        url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + cloudAccount + '/resources?deep'),
         type: 'GET',
         headers: requestHeaders,
         forever: constants.FOREVER_ON,
@@ -280,11 +280,11 @@ Cloud.getPools = function(req) {
     var requestHeaders = {};
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
 
     request({
-        url: utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + cloudAccount + '/pools',
+        url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + cloudAccount + '/pools'),
         type: 'GET',
         headers: requestHeaders,
         forever: constants.FOREVER_ON,
index 8d1734b..ebca59a 100644 (file)
@@ -37,11 +37,11 @@ ConfigAgentAccount.get = function(req) {
             var requestHeaders = {};
             _.extend(requestHeaders,
                 constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 });
 
             request({
-                    url: utils.confdPort(api_server) + '/api/operational/config-agent/account',
+                    url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operational/config-agent/account'),
                     type: 'GET',
                     headers: requestHeaders,
                     forever: constants.FOREVER_ON,
@@ -77,11 +77,11 @@ ConfigAgentAccount.get = function(req) {
             var requestHeaders = {};
             _.extend(requestHeaders,
                 constants.HTTP_HEADERS.accept.data, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 });
 
             request({
-                    url: utils.confdPort(api_server) + '/api/operational/config-agent/account/' + id,
+                    url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operational/config-agent/account/' + id),
                     type: 'GET',
                     headers: requestHeaders,
                     forever: constants.FOREVER_ON,
@@ -130,11 +130,11 @@ ConfigAgentAccount.create = function(req) {
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
 
         request({
-            url: utils.confdPort(api_server) + '/api/config/config-agent',
+            url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/config-agent'),
             method: 'POST',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -169,11 +169,11 @@ ConfigAgentAccount.update = function(req) {
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
 
         request({
-            url: utils.confdPort(api_server) + '/api/config/config-agent/account/' + id,
+            url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/config-agent/account/' + id),
             method: 'PUT',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -211,10 +211,10 @@ ConfigAgentAccount.delete = function(req) {
         var requestHeaders = {};
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
         request({
-            url: utils.confdPort(api_server) + '/api/config/config-agent/account/' + id,
+            url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/config-agent/account/' + id),
             method: 'DELETE',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
index 6757c28..6d928ca 100644 (file)
@@ -38,10 +38,10 @@ Sdn.get = function(req) {
       var requestHeaders = {};
       _.extend(requestHeaders,
         constants.HTTP_HEADERS.accept.collection, {
-          'Authorization': req.get('Authorization')
+          'Authorization': req.session && req.session.authorization
         });
       request({
-          url: utils.confdPort(api_server) + '/api/operational/sdn/account?deep',
+          url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operational/sdn/account?deep'),
           type: 'GET',
           headers: requestHeaders,
           forever: constants.FOREVER_ON,
@@ -77,11 +77,11 @@ Sdn.get = function(req) {
       var requestHeaders = {};
       _.extend(requestHeaders,
         constants.HTTP_HEADERS.accept.data, {
-          'Authorization': req.get('Authorization')
+          'Authorization': req.session && req.session.authorization
         });
 
       request({
-          url: utils.confdPort(api_server) + '/api/operational/sdn/account/' + id + '?deep',
+          url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operational/sdn/account/' + id + '?deep'),
           type: 'GET',
           headers: requestHeaders,
           forever: constants.FOREVER_ON,
@@ -130,11 +130,11 @@ Sdn.create = function(req) {
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data,
       constants.HTTP_HEADERS.content_type.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
 
     request({
-      url: utils.confdPort(api_server) + '/api/config/sdn/account',
+      url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/sdn/account'),
       method: 'POST',
       headers: requestHeaders,
       forever: constants.FOREVER_ON,
@@ -168,11 +168,11 @@ Sdn.update = function(req) {
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data,
       constants.HTTP_HEADERS.content_type.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
 
     request({
-      url: utils.confdPort(api_server) + '/api/config/sdn/account/' + id,
+      url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/sdn/account/' + id),
       method: 'PUT',
       headers: requestHeaders,
       forever: constants.FOREVER_ON,
@@ -211,11 +211,11 @@ Sdn.delete = function(req) {
     var requestHeaders = {};
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
 
     request({
-      url: utils.confdPort(api_server) + '/api/config/sdn/account/' + id,
+      url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/sdn/account/' + id),
       method: 'DELETE',
       headers: requestHeaders,
       forever: constants.FOREVER_ON,
index 81bb702..3582888 100644 (file)
@@ -2,8 +2,9 @@
     "root": "public",
     "name": "Accounts",
     "dashboard": "./account/accountsDashboard.jsx",
-    "order": 1,
+    "order": 3,
     "priority":1,
+    "allow": ["rw-rbac-platform:super-admin", "rw-project-mano:account-oper", "rw-project-mano:account-admin"],
     "routes": [
     {
         "label": "Accounts Dashboard",
index 4f011cb..1916ef3 100644 (file)
@@ -23,6 +23,7 @@ import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx'
 import Crouton from 'react-crouton';
 import TextInput from 'widgets/form_controls/textInput.jsx';
 import {AccountConnectivityStatus} from '../account_sidebar/accountSidebar.jsx';
+
 import 'style/common.scss';
 import './account.scss';
 class Account extends React.Component {
@@ -263,7 +264,16 @@ class Account extends React.Component {
                     value = Account.params[node.ref];
                 }
                 paramsStack.push(
-                    <TextInput key={node.label} className="accountForm-input" label={node.label} required={!node.optional}  onChange={this.props.store.handleParamChange(node)} value={value} />
+                    <TextInput
+                        key={node.label}
+                        className="accountForm-input"
+                        label={node.label}
+                        required={!node.optional}
+                        onChange={this.props.store.handleParamChange(node)}
+                        value={value}
+                        readonly={self.props.readonly}
+
+                        />
                 );
             }
 
@@ -287,7 +297,16 @@ class Account extends React.Component {
                     //     </label>
                     // );
                     nestedParamsStack.push(
-                          <TextInput key={node.label} label={node.label} required={!node.optional} className="create-fleet-pool-input" type="text" onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)} value={value}/>
+                          <TextInput
+                            key={node.label}
+                            label={node.label}
+                            required={!node.optional}
+                            className="create-fleet-pool-input"
+                            type="text"
+                            onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)}
+                            value={value}
+                            readonly={self.props.readonly}
+                            />
                     );
                 }
             }
@@ -330,7 +349,7 @@ class Account extends React.Component {
                             <AccountConnectivityStatus status={Account['connection-status'].status} />
                             {Account['connection-status'] && Account['connection-status'].status &&  Account['connection-status'].status.toUpperCase()}
                         </div>
-                            <Button className="refreshList light" onClick={this.props.store.refreshAccount.bind(this, Account.name, AccountType)} label="REFRESH STATUS"></Button>
+                            <Button is-disabled={self.props.readonly} className="refreshList light" onClick={this.props.store.refreshAccount.bind(this, Account.name, AccountType)} label="REFRESH STATUS"></Button>
                     </div>
                     {
                         (Account['connection-status'] && Account['connection-status'].status && Account['connection-status'].status.toUpperCase()) === 'FAILURE' ?
@@ -387,7 +406,7 @@ class Account extends React.Component {
                       {params}
                   </ol>
                   <div className="form-actions">
-                      {buttons}
+                      {!self.props.readonly ? buttons : null}
                   </div>
               </form>
         )
@@ -395,6 +414,11 @@ class Account extends React.Component {
     }
 }
 
+Account.contextTypes = {
+    router: React.PropTypes.object,
+    userProfile: React.PropTypes.object
+}
+
 function displayFailureMessage(msg) {
     return (
         <div className="accountForm-content" style={{maxWidth: '600px'}}>
index aeb5f48..22e8a17 100644 (file)
@@ -21,6 +21,7 @@ import AccountSource from './accountSource.js';
 var Utils = require('utils/utils.js');
 var rw = require('utils/rw.js');
 var altImage = rw.getSearchParams(window.location).alt_image;
+var _ = require('lodash');
 
 let Params = {
     //Config Agent
@@ -291,7 +292,7 @@ export default class AccountStore {
                 if(self.currentAccount) {
                     let Account = self.getAccountFromStream(data[self.currentAccount.type].data, self.currentAccount.name);
                     newState.account = self.account;
-                    newState.account['connection-status'] = Account['connection-status']
+                    newState.account['connection-status'] = Account && Account['connection-status']
                 }
                 self.setState(newState)
             } catch(error) {
@@ -332,7 +333,7 @@ export default class AccountStore {
     }
     getAccountFromStream(data, name) {
         let result = null;
-        data.map(function(a) {
+        data && _.isArray(data) && data.map(function(a) {
             if(a.name == name) {
                 result = a;
             }
@@ -342,7 +343,7 @@ export default class AccountStore {
     viewAccount = ({type, name}) => {
         var data = null;
         var accounts = null;
-        if(this && this[type].length) {
+        if(this && this[type] && this[type].length) {
             accounts = this[type];
             data = this.getAccountFromStream(accounts, name);
             if(data) {
@@ -364,7 +365,7 @@ export default class AccountStore {
     }
     generateOptionsByName(data) {
         let results = [];
-        if (data && data.constructor.name == "Array") {
+        if (data && _.isArray(data)) {
           data.map(function(d) {
               results.push({
                   label: d.name,
index f139b47..c95a63f 100644 (file)
@@ -21,6 +21,14 @@ import AppHeader from 'widgets/header/header.jsx';
 import AccountStore from './accountStore.js';
 import AccountSidebar from '../account_sidebar/accountSidebar.jsx';
 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
+//Delete this line after testing is done
+// PROJECT_ROLES.ACCOUNT_ADMIN = '';
 import 'style/layout.scss';
 
 class AccountsDashboard extends React.Component {
@@ -44,11 +52,12 @@ class AccountsDashboard extends React.Component {
     render() {
         let self = this;
         let html;
+        let READONLY = !isRBACValid(this.context.userProfile, [PROJECT_ROLES.ACCOUNT_ADMIN]);
         html = (<div className="launchpad-account-dashboard content-wrapper">
                     <div className="flex">
-                      <AccountSidebar {...this.state} store={this.Store}/>
+                      <AccountSidebar {...this.state} readonly={READONLY} store={this.Store}/>
                       <div>
-                        { this.props.children ? React.cloneElement(this.props.children, {store: self.Store, ...self.state}) : 'Edit or Create New Accounts'
+                        { this.props.children ? React.cloneElement(this.props.children, {readonly: READONLY, store: self.Store, ...self.state}) : 'Edit or Create New Accounts'
                         }
                       </div>
                     </div>
@@ -57,7 +66,8 @@ class AccountsDashboard extends React.Component {
     }
 }
 AccountsDashboard.contextTypes = {
-    router: React.PropTypes.object
+    router: React.PropTypes.object,
+    userProfile: React.PropTypes.object
 };
 
 export default SkyquakeComponent(AccountsDashboard);
index 723d893..3a10ce9 100644 (file)
@@ -42,6 +42,7 @@ class AccountSidebar extends React.Component{
     }
     render() {
         let html;
+        let self = this;
         let {store, ...props} = this.props;
         //[this.props.cloud,this.props.sdn,this.props['config-agent']]
         let AccountData = [
@@ -128,42 +129,53 @@ class AccountSidebar extends React.Component{
                     <div>
                         <h1>VIM Accounts</h1>
                         {cloudAccounts}
-                        <DashboardCard className="accountSidebarCard">
-                                <Link
-                                to={{pathname: '/accounts/cloud/create'}}
-                                title="Create Cloud Account"
-                                className={'accountSidebarCard_create'}
-                            >
-                                    Add VIM Account
-                                    <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
-                                </Link>
-                        </DashboardCard>
+                        {
+                            !self.props.readonly ?
+                                <DashboardCard className="accountSidebarCard">
+                                    <Link
+                                        to={{pathname: '/accounts/cloud/create'}}
+                                        title="Create Cloud Account"
+                                        className={'accountSidebarCard_create'} >
+                                            Add VIM Account
+                                            <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
+                                    </Link>
+                                </DashboardCard>
+                            :  <div style={{margin:'1rem'}}></div>
+                        }
                     </div>)
                 : null}
                 <h1>SDN Accounts</h1>
                 {sdnAccounts}
-                <DashboardCard className="accountSidebarCard">
-                        <Link
-                        to={{pathname: '/accounts/sdn/create'}}
-                        title="Create Sdn Account"
-                        className={'accountSidebarCard_create'}
-                    >
-                            Add SDN Account
-                            <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
-                        </Link>
-                </DashboardCard>
+                {
+                    !self.props.readonly ?
+                        <DashboardCard className="accountSidebarCard">
+                            <Link
+                            to={{pathname: '/accounts/sdn/create'}}
+                            title="Create Sdn Account"
+                            className={'accountSidebarCard_create'}>
+                                Add SDN Account
+                                <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
+                            </Link>
+
+                        </DashboardCard>
+                    : <div style={{margin:'1rem'}}></div>
+                }
                 <h1>Config Agent Accounts</h1>
                 {configAgentAccounts}
-                <DashboardCard className="accountSidebarCard">
-                    <Link
-                        to={{pathname: '/accounts/config-agent/create'}}
-                        title="Create Config Agent Account"
-                        className={'accountSidebarCard_create'}
-                    >
-                            Add Config Agent Account
-                            <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
-                        </Link>
-                </DashboardCard>
+                {
+                    !self.props.readonly ?
+                        <DashboardCard className="accountSidebarCard">
+                            <Link
+                                to={{pathname: '/accounts/config-agent/create'}}
+                                title="Create Config Agent Account"
+                                className={'accountSidebarCard_create'}
+                            >
+                                Add Config Agent Account
+                                <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
+                            </Link>
+                        </DashboardCard>
+                    :  <div style={{margin:'1rem'}}></div>
+                }
             </div>
                 );
         return html;
index 5ff92c8..f2409d6 100644 (file)
@@ -35,33 +35,34 @@ var DataCenters = {};
 Composer.get = function(req) {
     var api_server = req.query['api_server'];
     var results = {}
+    var projectPrefix = req.session.projectId ? "project-" : "";
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep',
+                uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep'),
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true
             }),
             rp({
-                uri: utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd?deep',
+                uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd?deep'),
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true
             }),
             rp({
-                uri: utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata?deep',
+                uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata?deep'),
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
@@ -74,7 +75,7 @@ Composer.get = function(req) {
             //   headers: _.extend({},
             //     constants.HTTP_HEADERS.accept.collection,
             //     {
-            //       'Authorization': req.get('Authorization')
+            //       'Authorization': req.session && req.session.authorization
             //     }),
             //   forever: constants.FOREVER_ON,
             // rejectUnauthorized: false,
@@ -122,7 +123,7 @@ Composer.get = function(req) {
                 "descriptors": []
             }];
             if (result[0].body) {
-                response[0].descriptors = JSON.parse(result[0].body).collection['nsd:nsd'];
+                response[0].descriptors = JSON.parse(result[0].body).collection[projectPrefix + 'nsd:nsd'];
                 if (result[2].body) {
                     var data = JSON.parse(result[2].body);
                     if (data && data["nsr:ns-instance-opdata"] && data["nsr:ns-instance-opdata"]["rw-nsr:nsd-ref-count"]) {
@@ -142,10 +143,10 @@ Composer.get = function(req) {
                 }
             };
             if (result[1].body) {
-                response[1].descriptors = JSON.parse(result[1].body).collection['vnfd:vnfd'];
+                response[1].descriptors = JSON.parse(result[1].body).collection[projectPrefix + 'vnfd:vnfd'];
             };
             // if (result[2].body) {
-            //   response[2].descriptors = JSON.parse(result[2].body).collection['pnfd:pnfd'];
+            //   response[2].descriptors = JSON.parse(result[2].body).collection[projectPrefix + 'pnfd:pnfd'];
             // };
             resolve({
                 statusCode: response.statusCode || 200,
@@ -171,10 +172,10 @@ Composer.delete = function(req) {
     console.log('Deleting', catalogType, id, 'from', api_server);
     return new Promise(function(resolve, reject) {
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog/' + catalogType + '/' + id,
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog/' + catalogType + '/' + id),
             method: 'DELETE',
             headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             forever: constants.FOREVER_ON,
             rejectUnauthorized: false,
@@ -190,7 +191,7 @@ Composer.delete = function(req) {
 Composer.getVNFD = function(req) {
     var api_server = req.query['api_server'];
     var vnfdID = req.body.data;
-    var authorization = req.get('Authorization');
+    var authorization = req.session && req.session.authorization;
     var VNFDs = [];
     if (typeof(vnfdID) == "object" && vnfdID.constructor.name == "Array") {
         vnfdID.map(function(id) {
@@ -219,7 +220,7 @@ Composer.getVNFD = function(req) {
         return new Promise(function(resolve, reject) {
             var url = utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd' + (id ? '/' + id : '') + '?deep';
             request({
-                uri: url,
+                uri: utils.projectContextUrl(req, url),
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
                     'Authorization': authorization
@@ -255,10 +256,10 @@ Composer.create = function(req) {
     return new Promise(function(resolve, reject) {
         var requestHeaders = {};
         _.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         });
         request({
-            uri: utils.confdPort(api_server) + '/api/config/' + catalogType + '-catalog',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/' + catalogType + '-catalog'),
             method: 'POST',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -285,10 +286,10 @@ Composer.updateSave = function(req) {
     return new Promise(function(resolve, reject) {
         var requestHeaders = {};
         _.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         });
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog' + '/' + catalogType + '/' + id,
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog' + '/' + catalogType + '/' + id),
             method: 'PUT',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -316,24 +317,30 @@ PackageManager.upload = function(req) {
         download_host = req.protocol + '://' + req.get('host');//req.api_server + ':' + utils.getPortForProtocol(req.protocol);
     }
 
+    var input = {
+        'external-url': download_host + '/composer/upload/' + req.file.filename,
+        'package-type': 'VNFD',
+        'package-id': uuid()
+    }
+
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-create');
+
+    input = utils.addProjectContextToRPCPayload(req, uri, input);
+
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/package-create',
+                uri: uri,
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true,
                 json: true,
                 body: {
-                    input: {
-                        'external-url': download_host + '/composer/upload/' + req.file.filename,
-                        'package-type': 'VNFD',
-                        'package-id': uuid()
-                    }
+                    input: input
                 }
             })
         ]).then(function(result) {
@@ -341,7 +348,7 @@ PackageManager.upload = function(req) {
             data['transaction_id'] = result[0].body['output']['transaction-id'];
 
             // Add a status checker on the transaction and then to delete the file later
-            PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id']);
+            PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id'], true);
 
             // Return status to composer UI to update the status.
             resolve({
@@ -375,14 +382,19 @@ PackageManager.update = function(req) {
         'external-url': download_host + '/composer/update/' + req.file.filename,
         'package-type': 'VNFD',
         'package-id': uuid()
-    }
+    };
+
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-update');
+
+    input = utils.addProjectContextToRPCPayload(req, uri, input);
+
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/package-update',
+                uri: uri,
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
@@ -419,19 +431,22 @@ PackageManager.update = function(req) {
 PackageManager.export = function(req) {
     // /api/operations/package-export
     var api_server = req.query['api_server'];
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-export');
+    var input = req.body;
+    input = utils.addProjectContextToRPCPayload(req, uri, input);
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
                 uri: utils.confdPort(api_server) + '/api/operations/package-export',
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true,
                 json: true,
-                body: { "input": req.body}
+                body: { "input": input }
             })
         ]).then(function(result) {
             var data = {};
@@ -454,10 +469,14 @@ PackageManager.export = function(req) {
 PackageManager.copy = function(req) {
     // /api/operations/package-copy
     var api_server = req.query['api_server'];
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-copy');
+    var input = req.body;
+    input = utils.addProjectContextToRPCPayload(req, uri, input);
+
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/package-copy',
+                uri: uri,
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
                     'Authorization': req.get('Authorization')
@@ -466,7 +485,7 @@ PackageManager.copy = function(req) {
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true,
                 json: true,
-                body: { "input": req.body}
+                body: { "input": input}
             })
         ]).then(function(result) {
             var data = {};
@@ -495,7 +514,7 @@ PackageManager.getJobStatus = function(req) {
     var api_server = req.query["api_server"];
     var uri = utils.confdPort(api_server);
     var id = req.params['id'];
-    var url = uri + '/api/operational/copy-jobs' + (id ? '/job/' + id : '');
+    var url = utils.projectContextUrl(req, uri + '/api/operational/copy-jobs' + (id ? '/job/' + id : ''));
     return new Promise(function(resolve, reject) {
         request({
             url: url,
@@ -538,11 +557,17 @@ FileManager.addFile = function(req) {
         'package-type': package_type,
         'package-id': package_id,
         'package-path': package_path + '/' + req.file.filename
-    }
+    };
+
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-file-add');
+
+    input = utils.addProjectContextToRPCPayload(req, uri, input);
+
+
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/package-file-add',
+                uri: uri,
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
                     'Authorization': req.get('Authorization')
@@ -601,12 +626,14 @@ FileManager.get = function(req) {
     }
 
     function deleteFile(payload) {
+        var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/rw-pkg-mgmt:package-file-delete');
+        payload.input = utils.addProjectContextToRPCPayload(req, uri, payload.input);
         return new Promise(function(resolve, reject) {
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/rw-pkg-mgmt:package-file-delete',
+                uri: uri,
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 json: payload,
                 forever: constants.FOREVER_ON,
@@ -623,12 +650,14 @@ FileManager.get = function(req) {
         })
     }
     function download(payload) {
+        var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/rw-pkg-mgmt:package-file-add');
+        payload.input = utils.addProjectContextToRPCPayload(req, uri, payload.input);
         return new Promise(function(resolve, reject) {
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/rw-pkg-mgmt:package-file-add',
+                uri: uri,
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 json: payload,
                 forever: constants.FOREVER_ON,
@@ -645,12 +674,14 @@ FileManager.get = function(req) {
         })
     }
     function list(payload) {
+        var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/get-package-endpoint');
+        payload.input = utils.addProjectContextToRPCPayload(req, uri, payload.input);
         return new Promise(function(resolve, reject) {
             rp({
-                uri: utils.confdPort(api_server) + '/api/operations/get-package-endpoint',
+                uri: uri,
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 json: payload,
                 forever: constants.FOREVER_ON,
@@ -668,10 +699,10 @@ FileManager.get = function(req) {
                     }
                     parsedEndpoint = URL.parse(endpoint);
                     rp({
-                        uri: api_server + ':' + parsedEndpoint.port + parsedEndpoint.path,
+                        uri: utils.projectContextUrl(req, api_server + ':' + parsedEndpoint.port + parsedEndpoint.path),
                         method: 'GET',
                         headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                            'Authorization': req.get('Authorization')
+                            'Authorization': req.session && req.session.authorization
                         }),
                         forever: constants.FOREVER_ON,
                         rejectUnauthorized: false,
@@ -699,10 +730,10 @@ FileManager.job = function(req) {
     var id = req.params['id'];
     return new Promise(function(resolve, reject) {
         request({
-            url: uri + url + '?deep',
+            url: utils.projectContextUrl(req, uri + url + '?deep'),
             method: 'GET',
             headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             forever: constants.FOREVER_ON,
             rejectUnauthorized: false,
index cc9cca7..b38abde 100644 (file)
@@ -6,7 +6,7 @@ var constants = require('../../../framework/core/api_utils/constants.js');
 var fs = require('fs');
 var _ = require('lodash');
 
-PackageFileHandler = {};
+var PackageFileHandler = {};
 
 function deleteFile(filename) {
        setTimeout(function() {
@@ -18,7 +18,7 @@ function checkStatus(req, transactionId, isUpdate) {
        var upload_server = req.query['upload_server'];
        var headers = _.extend({},
         {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         }
     );
     var type = isUpdate ? 'update' : 'upload';
index 80a2a0c..34b1084 100644 (file)
@@ -5,6 +5,10 @@
     "name": "Catalog",
     "dashboard" : "./src/components/ComposerApp.js",
     "order": 2,
+    "allow": [
+        "rw-rbac-platform:super-admin",
+        "rw-project-mano:catalog-oper",
+        "rw-project-mano:catalog-admin"],
     "routes" : [{
         "label": "Catalog",
         "route": "/",
index 76a569f..65b90fd 100644 (file)
@@ -40,6 +40,7 @@ const Button = React.createClass({
                        label: null,
                        title: null,
                        src: null,
+                       disabled: false,
                        onClick: () => {}
                };
        },
@@ -57,8 +58,15 @@ const Button = React.createClass({
                const title = this.props.title;
                const draggable = this.props.draggable;
                const className = ClassNames(this.props.className, 'Button');
+               let style = {
+               }
+               if(this.props.disabled) {
+                       style.pointerEvents = 'none';
+                       style.cursor = 'not-allowed';
+                       style.opacity = 0.25;
+               }
                return (
-                       <div className={className} onClick={this.props.onClick} title={title} draggable={draggable} onDragStart={this.props.onDragStart}>
+                       <div className={className} onClick={this.props.onClick} title={title} draggable={draggable} onDragStart={this.props.onDragStart} style={style}>
                                { src ? <img src={src} /> : null }
                                {label}
                        </div>
index 752d678..484e936 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
 import React from 'react'
 import PureRenderMixin from 'react-addons-pure-render-mixin'
 import EditDescriptorModelProperties from './EditDescriptorModelProperties'
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
 
 const CatalogItemDetailsEditor = React.createClass({
        mixins: [PureRenderMixin],
@@ -32,6 +36,10 @@ const CatalogItemDetailsEditor = React.createClass({
                        width: 0
                };
        },
+       contextTypes: {
+           router: React.PropTypes.object,
+           userProfile: React.PropTypes.object
+       },
        componentWillMount() {
        },
        componentDidMount() {
@@ -41,6 +49,7 @@ const CatalogItemDetailsEditor = React.createClass({
        componentWillUnmount() {
        },
        render() {
+       const User = this.context.userProfile;
 
                const container = this.props.container || {model: {}, uiState: {}};
                if (!(container && container.model && container.uiState)) {
@@ -51,7 +60,11 @@ const CatalogItemDetailsEditor = React.createClass({
                        <div className="CatalogItemDetailsEditor">
                                <form name="details-descriptor-editor-form">
                                        <div className="properties-group">
-                                               <EditDescriptorModelProperties container={this.props.container} width={this.props.width} />
+                                       {
+                                               isRBACValid(User, [PROJECT_ROLES.CAT_ADMIN]) ?
+                                                       <EditDescriptorModelProperties container={this.props.container} width={this.props.width} />
+                                               : <EditDescriptorModelProperties container={this.props.container} width={this.props.width} readonly={true} />
+                                       }
                                        </div>
                                </form>
                        </div>
@@ -59,5 +72,4 @@ const CatalogItemDetailsEditor = React.createClass({
 
        }
 });
-
 export default CatalogItemDetailsEditor;
index 0314e19..6d58c8a 100644 (file)
@@ -117,7 +117,7 @@ const CatalogPanel = React.createClass({
                const isLoading = this.props.isLoading;
                return (
                        <div className={className} data-resizable="right" data-resizable-handle-offset="0 6" style={{width: this.props.layout.left}}>
-                               <CatalogPanelToolbar />
+                               <CatalogPanelToolbar rbacDisabled={this.props.rbacDisabled} />
                                <div className="CatalogPanelBody">
                                        {(() => {
                                                if (isLoading) {
index 1501ecd..bc8db58 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,6 +32,10 @@ import imgOnboard from '../../../node_modules/open-iconic/svg/cloud-upload.svg'
 import imgUpdate from '../../../node_modules/open-iconic/svg/rain.svg'
 import imgDownload from '../../../node_modules/open-iconic/svg/cloud-download.svg'
 import imgDelete from '../../../node_modules/open-iconic/svg/trash.svg'
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
 
 const CatalogHeader = React.createClass({
        mixins: [PureRenderMixin],
@@ -48,28 +52,32 @@ const CatalogHeader = React.createClass({
        },
        componentWillUnmount() {
        },
+       contextTypes: {
+           userProfile: React.PropTypes.object
+       },
        render() {
+               const disabled = !isRBACValid(this.context.userProfile, [PROJECT_ROLES.CAT_ADMIN]);
                return (
                        <div className="CatalogPanelToolbar">
                                <h1>Descriptor Catalogs</h1>
                                <div className="btn-bar">
                                        <div className="btn-group">
-                                               <Button type="image" title="OnBoard a catalog package" className="action-onboard-catalog-package" onClick={this.onClickOnBoardCatalog} src={imgOnboard} />
-                                               <Button type="image" title="Update a catalog package" className="action-update-catalog-package" onClick={this.onClickUpdateCatalog} src={imgUpdate} />
-                                               <Button type="image" title="Export selected catalog item(s)" className="action-export-catalog-items" onClick={this.onClickExportCatalogItems} src={imgDownload} />
+                                               <Button type="image" title="OnBoard a catalog package" className="action-onboard-catalog-package" onClick={this.onClickOnBoardCatalog} src={imgOnboard} disabled={disabled}/>
+                                               <Button type="image" title="Update a catalog package" className="action-update-catalog-package" onClick={this.onClickUpdateCatalog} src={imgUpdate}  disabled={disabled}/>
+                                               <Button type="image" title="Export selected catalog item(s)" className="action-export-catalog-items" onClick={this.onClickExportCatalogItems} src={imgDownload}  disabled={disabled}/>
                                        </div>
                                        <div className="btn-group">
                                                <div className="menu">
-                                                       <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'nsd')} src={imgAdd} />
+                                                       <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'nsd')} src={imgAdd}  disabled={disabled}/>
                                                        <div className="sub-menu">
-                                                               <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'nsd')} src={imgAdd} label="Add NSD" />
-                                                               <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'vnfd')} src={imgAdd} label="Add VNFD" />
+                                                               <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'nsd')} src={imgAdd} label="Add NSD"  disabled={disabled}/>
+                                                               <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'vnfd')} src={imgAdd} label="Add VNFD"  disabled={disabled}/>
                                                        </div>
                                                </div>
-                                               <Button type="image" title="Copy catalog item" className="action-copy-catalog-item" onClick={this.onClickDuplicateCatalogItem} src={imgCopy} />
+                                               <Button type="image" title="Copy catalog item" className="action-copy-catalog-item" onClick={this.onClickDuplicateCatalogItem} src={imgCopy}  disabled={disabled}/>
                                        </div>
                                        <div className="btn-group">
-                                               <Button type="image" title="Delete catalog item" className="action-delete-catalog-item" onClick = {this.onClickDeleteCatalogItem} src={imgDelete} />
+                                               <Button type="image" title="Delete catalog item" className="action-delete-catalog-item" onClick = {this.onClickDeleteCatalogItem} src={imgDelete}  disabled={disabled}/>
                                        </div>
                                </div>
                        </div>
index 861c38b..629f409 100644 (file)
@@ -48,6 +48,9 @@ import TooltipManager from '../libraries/TooltipManager'
 import CatalogItemsActions from '../actions/CatalogItemsActions'
 import CommonUtils from 'utils/utils.js'
 import FileManagerActions from './filemanager/FileManagerActions';
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+
 import 'normalize.css'
 import '../styles/AppRoot.scss'
 import 'style/layout.scss'
@@ -60,6 +63,8 @@ const clearLocalStorage = utils.getSearchParams(window.location).hasOwnProperty(
 const preventDefault = e => e.preventDefault();
 const clearDragState = () => ComposerAppActions.setDragState(null);
 
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
 
 const ComposerApp = React.createClass({
        mixins: [PureRenderMixin],
@@ -69,6 +74,10 @@ const ComposerApp = React.createClass({
        getDefaultProps() {
                return {};
        },
+       contextTypes: {
+           router: React.PropTypes.object,
+           userProfile: React.PropTypes.object
+       },
        componentWillMount() {
                if (clearLocalStorage) {
                        window.localStorage.clear();
@@ -147,6 +156,8 @@ const ComposerApp = React.createClass({
        render() {
                let html = null;
                let self = this;
+               const User = this.context.userProfile || {};
+               const rbacDisabled = !isRBACValid(User, [PROJECT_ROLES.CAT_ADMIN]);
                if(this.state.hasModel) {
 
                        function onClickUpdateSelection(event) {
@@ -195,7 +206,8 @@ const ComposerApp = React.createClass({
                                                        <CatalogPanel layout={self.state.layout}
                                                                                  isLoading={isLoading}
                                                                                  hasNoCatalogs={hasNoCatalogs}
-                                                                                 filterByType={self.state.filterCatalogByTypeValue} />
+                                                                                 filterByType={self.state.filterCatalogByTypeValue}
+                                                                                 rbacDisabled={rbacDisabled} />
                                                        <CanvasPanel layout={self.state.layout}
                                                                                 hasNoCatalogs={hasNoCatalogs}
                                                                                 showMore={self.state.showMore}
@@ -208,6 +220,7 @@ const ComposerApp = React.createClass({
                                          newPathName={self.state.newPathName}
                                                                                 item={self.state.item}
                                                                                 type={self.state.filterCatalogByTypeValue}
+                                                                                rbacDisabled={rbacDisabled}
                                                                                  />
                                                        {
                                                                (self.state.panelTabShown == 'descriptor') ?
@@ -225,7 +238,7 @@ const ComposerApp = React.createClass({
                                                                                                isEditingVNFD={isEditingVNFD}
                                                                                                isModified={isModified}
                                                                                                isNew={isNew}
-                                                                                               disabled={!hasItem}
+                                                                                               disabled={!hasItem || rbacDisabled}
                                                                                                onClick={event => event.stopPropagation()}
                                                                                                panelTabShown={self.state.panelTabShown}/>
                                                </div>
@@ -270,4 +283,5 @@ const ComposerApp = React.createClass({
 
 });
 
+
 export default ComposerApp;
index 458e774..c1302ab 100644 (file)
@@ -136,11 +136,8 @@ const ComposerAppToolbar = React.createClass({
                const style = {left: this.props.layout.left};
                const saveClasses = ClassNames('ComposerAppSave', {'primary-action': this.props.isModified || this.props.isNew});
                const cancelClasses = ClassNames('ComposerAppCancel', {'secondary-action': this.props.isModified});
-               if (this.props.disabled) {
-                       return (
-                               <div className="ComposerAppToolbar" style={style}></div>
-                       );
-               }
+               let isDisabled = this.props.disabled;
+               console.log('rbacDisabled', isDisabled )
                const hasSelection = SelectionManager.getSelections().length > 0;
                if(this.props.panelTabShown != 'descriptor') {
                        style.pointerEvents = 'none';
@@ -156,35 +153,38 @@ const ComposerAppToolbar = React.createClass({
                                        if (this.props.isEditingNSD || this.props.isEditingVNFD) {
                                                return (
                                                        <div className="FileActions">
-                                                               <Button className={saveClasses} onClick={this.onClickSave} label={messages.getSaveActionLabel(this.props.isNew)} src={imgSave} />
-                                                               <Button className={cancelClasses} onClick={this.onClickCancel} label="Cancel" src={imgCancel} />
-                                                               <Button className="ComposerAppToggleJSONViewerAction" onClick={this.toggleJSONViewer} label="YAML Viewer" src={imgJSONViewer} />
+                                                               <Button className={saveClasses} onClick={this.onClickSave} label={messages.getSaveActionLabel(this.props.isNew)} src={imgSave} disabled={isDisabled} />
+                                                               <Button className={cancelClasses} onClick={this.onClickCancel} label="Cancel" src={imgCancel} disabled={isDisabled} />
+                                                               <Button className="ComposerAppToggleJSONViewerAction" onClick={this.toggleJSONViewer} label="YAML Viewer" src={imgJSONViewer} disabled={isDisabled} />
                                                        </div>
                                                );
                                        }
                                })()}
                                <div className="LayoutActions">
-                                       <Button className="action-auto-layout" onClick={this.onClickAutoLayout} label="Auto Layout" src={imgLayout} />
+                                       <Button className="action-auto-layout" onClick={this.onClickAutoLayout} label="Auto Layout" src={imgLayout} disabled={isDisabled} />
                                        {this.props.isEditingNSD ||
                                                this.props.isEditingVNFD ? <Button className="action-add-vld"
+                                                                                                                  disabled={isDisabled}
                                                                                                                   draggable="true"
                                                                                                                   label={this.props.isEditingNSD ? 'Add VLD' : 'Add IVLD'}
                                                                                                                   src={imgVLD}
                                                                                                                   onDragStart={this.onDragStartAddVld}
                                                                                                                   onClick={this.onClickAddVld} /> : null}
                                        {this.props.isEditingNSD ? <Button className="action-add-vnffg"
+                                                                                                          disabled={isDisabled}
                                                                                                           draggable="true"
                                                                                                           label="Add VNFFG"
                                                                                                           src={imgFG}
                                                                                                           onDragStart={this.onDragStartAddVnffg}
                                                                                                           onClick={this.onClickAddVnffg} /> : null}
                                        {this.props.isEditingVNFD ? <Button className="action-add-vdu"
+                                                                                                               disabled={isDisabled}
                                                                                                                draggable="true"
                                                                                                                label="Add VDU"
                                                                                                                src={imgVDU}
                                                                                                                onDragStart={this.onDragStartAddVdu}
                                                                                                                onClick={this.onClickAddVdu} /> : null}
-                                       <Button type="image" title="Delete selected items" className="action-delete-selected-items" disabled={!hasSelection} onClick = {this.onClickDeleteSelected} src={imgDelete} label="Delete" />
+                                       <Button type="image" title="Delete selected items" className="action-delete-selected-items" disabled={!hasSelection || isDisabled} onClick = {this.onClickDeleteSelected} src={imgDelete} label="Delete" />
                                </div>
                        </div>
                );
index 2e12052..e13282e 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -50,6 +50,10 @@ const DetailsPanel = React.createClass({
        },
        componentWillUnmount() {
        },
+       contextTypes: {
+           router: React.PropTypes.object,
+           userProfile: React.PropTypes.object
+       },
        render() {
                let json = '{}';
                let bodyComponent =  messages.detailsWelcome();
index c1d65be..22ba179 100644 (file)
@@ -78,6 +78,8 @@ function getTitle(model = {}) {
 export default function EditDescriptorModelProperties(props) {
 
        const container = props.container;
+       const readonly = props.readonly;
+       const isEditable = !readonly; //true
 
        if (!(DescriptorModelFactory.isContainer(container))) {
                return
@@ -148,6 +150,9 @@ export default function EditDescriptorModelProperties(props) {
                        }
                        CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
                }
+               if(readonly) {
+                       return null;
+               }
                return (
                                <Button className="inline-hint" onClick={onClickAddProperty.bind(container, property, path)} label="Add" src={imgAdd} />
                );
@@ -165,6 +170,9 @@ export default function EditDescriptorModelProperties(props) {
                        }
                        CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
                }
+               if(readonly) {
+                       return null;
+               }
                return (
                        <Button className="remove-property-action inline-hint" title="Remove" onClick={onClickRemoveProperty.bind(container, property, path)} label="Remove" src={imgRemove}/>
                );
@@ -233,7 +241,7 @@ export default function EditDescriptorModelProperties(props) {
                                        onBlur={endEditing} 
                                        onMouseDown={startEditing} 
                                        onMouseOver={startEditing} 
-                                       readOnly={!isEditable}>
+                                       disabled={!isEditable}>
                                                {options}
                                </select>
                        );
@@ -268,7 +276,7 @@ export default function EditDescriptorModelProperties(props) {
                                        onBlur={endEditing} 
                                        onMouseDown={startEditing} 
                                        onMouseOver={startEditing} 
-                                       readOnly={!isEditable}>
+                                       disabled={!isEditable}>
                                                {options}
                                </select>
                        );
@@ -301,7 +309,7 @@ export default function EditDescriptorModelProperties(props) {
                                        onBlur={endEditing} 
                                        onMouseDown={startEditing} 
                                        onMouseOver={startEditing} 
-                                       readOnly={!isEditable}>
+                                       disabled={!isEditable}>
                                                {options}
                                </select>
                        );
@@ -330,7 +338,7 @@ export default function EditDescriptorModelProperties(props) {
                                        onBlur={endEditing} 
                                        onMouseDown={startEditing} 
                                        onMouseOver={startEditing} 
-                                       readOnly={!isEditable}>
+                                       disabled={!isEditable}>
                                                {options}
                                </select>
                        );
@@ -351,7 +359,7 @@ export default function EditDescriptorModelProperties(props) {
                                        onMouseOver={startEditing} 
                                        onMouseOut={endEditing} 
                                        onMouseLeave={endEditing} 
-                                       readOnly={!isEditable} />
+                                       disabled={!isEditable} />
                        );
                }
 
@@ -370,7 +378,7 @@ export default function EditDescriptorModelProperties(props) {
                                onMouseOver={startEditing}
                                onMouseOut={endEditing}
                                onMouseLeave={endEditing}
-                               readOnly={!isEditable}
+                               disabled={!isEditable}
                        />
                );
 
@@ -428,7 +436,7 @@ export default function EditDescriptorModelProperties(props) {
                                // write the current choice value into the state
                                let choiceObject = utils.resolvePath(this.model, [name, selected].join('.'));
                                let isTopCase = false;
-                               if (!choiceObject) {
+                               if (choiceObject) {
                                        isTopCase = true;
                                        choiceObject = utils.resolvePath(this.model, [selected].join('.'));
                                }
@@ -553,6 +561,7 @@ export default function EditDescriptorModelProperties(props) {
                                        onMouseOver={startEditing} 
                                        onMouseOut={endEditing} 
                                        onMouseLeave={endEditing}
+                                       disabled={!isEditable}
                                >
                                        {options}
                                </select>
index 0f39d30..ba1e0cc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -45,6 +45,11 @@ import mapRecordServicePath from './mapRecordServicePath'
 import onCutDelegateToRemove from './onCutDelegateToRemove'
 import onClickSelectAndShowInDetailsPanel from './onClickSelectAndShowInDetailsPanel'
 
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
 import '../../styles/EditForwardingGraphPaths.scss'
 
 import imgNSD from '../../images/default-catalog-icon.svg'
@@ -119,7 +124,11 @@ function mapFG(fg, i) {
                <div key={i} className={fg.className} data-uid={fg.uid} data-offset-width="true" onClick={onClickSelectAndShowInDetailsPanel.bind(null, fg)} onCut={onCutDelegateToRemove.bind(null, fg)}>
                        <div key="outline-indicator" data-outline-indicator="true"></div>
                        <div className="header-actions">
-                               <Button className="remove-forwarding-graph" title="Remove" onClick={onClickRemoveForwardingGraph.bind(null, fg)} src={imgRemove}/>
+                               {
+                                       this.isRBACValid ?
+                                               <Button className="remove-forwarding-graph" title="Remove" onClick={onClickRemoveForwardingGraph.bind(null, fg)} src={imgRemove}/>
+                                               : null
+                               }
                        </div>
                        <LayoutRow primaryActionColumn={toggleSelectAllPaths} secondaryActionColumn={<img className="fg-icon" src={imgFG} width="20px" />}>
                                <small>{fg.title}</small>
@@ -133,7 +142,11 @@ function mapFG(fg, i) {
                                {fg.classifier.map(mapClassifier.bind(null, context))}
                                <div className="footer-actions">
                                        <div className="row-action-column">
-                                               <Button className="create-new-classifier" src={imgAdd} width="20px" onClick={onClickAddClassifier.bind(null, context, fg)} label="Add Classifier" />
+                                       {
+                                               this.isRBACValid ?
+                                                       <Button className="create-new-classifier" src={imgAdd} width="20px" onClick={onClickAddClassifier.bind(null, context, fg)} label="Add Classifier" />
+                                                       : null
+                                       }
                                        </div>
                                </div>
                        </div>
@@ -167,7 +180,11 @@ function mapNSD(nsd, i) {
                        {forwardingGraphs}
                        <div className="footer-actions">
                                <div className="row-action-column">
-                                       <Button className="create-new-forwarding-graph" src={imgAdd} width="20px" onClick={onClickAddForwardingGraph.bind(null, nsd)} label="Add new Forwarding Graph" />
+                               {
+                                       this.isRBACValid ?
+                                               <Button className="create-new-forwarding-graph" src={imgAdd} width="20px" onClick={onClickAddForwardingGraph.bind(null, nsd)} label="Add new Forwarding Graph" />
+                                               : null
+                               }
                                </div>
                        </div>
                </div>
@@ -193,14 +210,16 @@ const EditForwardingGraphPaths = React.createClass({
        },
        componentWillUnmount: function () {
        },
+       contextTypes: {
+           userProfile: React.PropTypes.object
+       },
        render() {
-
                const containers = this.props.containers;
                const context = {
                        component: this,
-                       containers: containers
+                       containers: containers,
+                       isRBACValid: isRBACValid(this.context.userProfile, [PROJECT_ROLES.CAT_ADMIN])
                };
-
                const networkService = containers.filter(d => d.type === 'nsd');
                if (networkService.length === 0) {
                        return <p className="welcome-message">No <img src={imgNSD} width="20px" /> NSD open in the canvas. Try opening an NSD.</p>;
diff --git a/skyquake/plugins/composer/src/src/libraries/PackageManagerApi.js b/skyquake/plugins/composer/src/src/libraries/PackageManagerApi.js
deleted file mode 100644 (file)
index 69fa4d6..0000000
+++ /dev/null
@@ -1,113 +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 guid from '../libraries/guid'
-import DropZone from 'dropzone'
-import Utils from '../libraries/utils'
-import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions'
-import ReactDOM from 'react-dom'
-import $ from 'jquery'
-
-const API_SERVER = Utils.getSearchParams(window.location).api_server;
-
-
-
-
-export default class PackageManager {
-    constructor(element, button, action) {
-        this.stagingArea = {
-            packages: {
-                ids: []
-            }
-        }
-        this.stagingAreaMonitor = null;
-    }
-    createStagingArea(type, name) {
-        return $.ajax({
-            url: Utils.getSearchParams(window.location).api_server + ':8008/api/operations/create-staging-area',
-            type: 'POST',
-            data: {
-                "input" : {
-                    // Package type not important for package upload.
-                    "package-type": type || "NSD",
-                    "name": name || "Package Staging Area"
-                }
-            },
-            error: function() {
-                console.log('Something went wrong creating the staging area: ', arguments)
-            }
-        }).then(function(data) {
-            /*
-            {
-              "output": {
-                "endpoint": "api/upload/85f8e2dc-638b-46e7-89cb-ee8de322066f",
-                "port": "4568"
-              }
-            }
-             */
-            const id = data.output.endpoint.split('/')[2];
-            const port = data.output.port;
-            this.stagingArea.packages.ids.push(id);
-            this.stagingArea.packages[id] = {
-                port: port
-            };
-            return data
-        })
-    }
-    monitoringStagingAreaSocket() {
-        let self = this;
-        if(self.stagingAreaMonitor) {
-            return self.stagingAreaMonitor;
-        }
-        new Promise(function(resolve, reject) {
-            $.ajax({
-                url: '/socket-polling',
-                type: 'POST',
-                beforeSend: Utils.addAuthorizationStub,
-                data: {
-                  url: 'launchpad/api/nsr?api_server=' + API_SERVER
-                },
-                success: function(data, textStatus, jqXHR) {
-                  Utils.checkAndResolveSocketRequest(data, resolve, reject, self.monitoringStagingAreaSocketHandler);
-                }
-            })
-        })
-
-        return undefined;
-    }
-    monitoringStagingAreaSocketHandler(connection) {
-        let self = this;
-        let ws = window.multiplexer.channel(connection);
-        if (!connection) return;
-        self.stagingAreaMonitor = connection;
-        ws.onmessage = function(socket) {
-            try {
-                Utils.checkAuthentication(data.statusCode, function() {
-                    ws.close();
-                });
-
-            } catch(e) {
-                console.log('An exception occurred in monitoringStagingAreaSocketHandler', e)
-            }
-        }
-    }
-
-}
-
-
-
index b0fcfc6..d39a138 100644 (file)
@@ -187,6 +187,9 @@ export default {
                                                console.log('Something went wrong while resolving a leafref. Reached a leaf with predicate.');
                                        } else {
                                                // contains no predicate
+                                               if (!objectCopy) {
+                                                       break;
+                                               }
                                                results.push(objectCopy[fragment]);
                                        }
                                }
@@ -243,6 +246,9 @@ export default {
                                                }
                                        } else {
                                                // contains no predicate
+                                               if (!objectCopy) {
+                                                       break;
+                                               }
                                                objectCopy = objectCopy[fragment];
                                                if (!objectCopy) {
                                                        // contains no value
index 8220f0a..9af34bf 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -67,4 +67,4 @@
                        font-size: 11px;
                }
        }
-}
\ No newline at end of file
+}
index 99fb1d7..aecd6f4 100644 (file)
@@ -18,12 +18,12 @@ return new Promise(function(resolve, reject) {
         _.extend(
             requestHeaders,
             id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }
         );
 
         request({
-                url: url + '?deep',
+                url: utils.projectContextUrl(req, url + '?deep'),
                 type: 'GET',
                 headers: requestHeaders,
                 forever: constants.FOREVER_ON,
@@ -71,10 +71,10 @@ function updateAccount(req) {
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
         request({
-            url: url,
+            url: utils.projectContextUrl(req, url),
             method: method,
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
index 6fd9f43..a861d72 100644 (file)
@@ -4,6 +4,7 @@
     "dashboard": "./dashboard/dashboard.jsx",
     "order": 1,
     "priority":1,
+    "admin_link": true,
     "routes": [
     {
         "label": "Configuration Dashboard",
index 5d6e0ea..b02ae88 100644 (file)
@@ -33,7 +33,7 @@ crashDetails.get = function(req) {
     var requestHeaders = {};
     _.extend(requestHeaders,
       constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
       });
     request({
         url: utils.confdPort(api_server) + APIVersion +'/api/operational/crash?deep',
index 6643aca..b27dd6b 100644 (file)
@@ -4,6 +4,7 @@
     "dashboard": "./crash.jsx",
     "order": 100,
     "priority":2,
+    "admin_link": true,
     "routes" : [{
         "label": "Debug",
         "route": "/",
index d74bb3e..e82fc7c 100644 (file)
@@ -48,18 +48,19 @@ APIConfig.NfviMetrics = ['vcpu', 'memory'];
 RPC.executeNSServicePrimitive = function(req) {
     var api_server = req.query['api_server'];
     return new Promise(function(resolve, reject) {
+        var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operations/exec-ns-service-primitive');
         var jsonData = {
-            "input": req.body
+            "input": utils.addProjectContextToRPCPayload(req, uri, req.body)
         };
 
         var headers = _.extend({},
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }
         );
         request({
-            url: utils.confdPort(api_server) + APIVersion + '/api/operations/exec-ns-service-primitive',
+            url: uri,
             method: 'POST',
             headers: headers,
             forever: constants.FOREVER_ON,
@@ -81,18 +82,20 @@ RPC.getNSServicePrimitiveValues = function(req) {
     // var nsr_id = req.body['nsr_id_ref'];
     // var nsConfigPrimitiveName = req.body['name'];
     return new Promise(function(resolve, reject) {
+        var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operations/get-ns-service-primitive-values');
+
         var jsonData = {
-            "input": req.body
+            "input": utils.addProjectContextToRPCPayload(req, uri, req.body)
         };
 
         var headers = _.extend({},
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }
         );
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/operations/get-ns-service-primitive-values',
+            uri: uri,
             method: 'POST',
             headers: headers,
             forever: constants.FOREVER_ON,
@@ -133,16 +136,20 @@ RPC.refreshAccountConnectionStatus = function(req) {
         }
     }
     jsonData.input[rpcInfo[Type].label] = Name;
+
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operations/' + rpcInfo[Type].rpc);
+
+    jsonData.input = utils.addProjectContextToRPCPayload(req, uri, jsonData.input);
+
     var headers = _.extend({},
         constants.HTTP_HEADERS.accept.data,
         constants.HTTP_HEADERS.content_type.data, {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         }
     );
     return new Promise(function(resolve, reject) {
-
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/operations/' + rpcInfo[Type].rpc,
+            uri: uri,
             method: 'POST',
             headers: headers,
             forever: constants.FOREVER_ON,
@@ -168,33 +175,34 @@ var DataCenters = {};
 Catalog.get = function(req) {
     var api_server = req.query['api_server'];
     var results = {}
+    var projectPrefix = req.session.projectId ? "project-" : "";
     return new Promise(function(resolve, reject) {
         Promise.all([
             rp({
-                uri: utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep',
+                uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep'),
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true
             }),
             rp({
-                uri: utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd?deep',
+                uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd?deep'),
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true
             }),
             rp({
-                uri: utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata?deep',
+                uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata?deep'),
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
@@ -207,7 +215,7 @@ Catalog.get = function(req) {
             //   headers: _.extend({},
             //     constants.HTTP_HEADERS.accept.collection,
             //     {
-            //       'Authorization': req.get('Authorization')
+            //       'Authorization': req.session && req.session.authorization
             //     }),
             //   forever: constants.FOREVER_ON,
             // rejectUnauthorized: false,
@@ -258,12 +266,12 @@ Catalog.get = function(req) {
             var vnfdCatalog = null;
             var vnfdDict = {};
             if (result[1].body) {
-                vnfdCatalog = JSON.parse(result[1].body).collection['vnfd:vnfd'].map(function(v, i) {
+                vnfdCatalog = JSON.parse(result[1].body).collection[projectPrefix + 'vnfd:vnfd'].map(function(v, i) {
                     vnfdDict[v.id] = v['short-name'] || v.name;
                 })
             }
             if (result[0].body) {
-                response[0].descriptors = JSON.parse(result[0].body).collection['nsd:nsd'];
+                response[0].descriptors = JSON.parse(result[0].body).collection[projectPrefix + 'nsd:nsd'];
                 if (result[2].body) {
                     var data = JSON.parse(result[2].body);
                     if (data && data["nsr:ns-instance-opdata"] && data["nsr:ns-instance-opdata"]["rw-nsr:nsd-ref-count"]) {
@@ -286,10 +294,10 @@ Catalog.get = function(req) {
                 }
             };
             if (result[1].body) {
-                response[1].descriptors = JSON.parse(result[1].body).collection['vnfd:vnfd'];
+                response[1].descriptors = JSON.parse(result[1].body).collection[projectPrefix + 'vnfd:vnfd'];
             };
             // if (result[2].body) {
-            //   response[2].descriptors = JSON.parse(result[2].body).collection['pnfd:pnfd'];
+            //   response[2].descriptors = JSON.parse(result[2].body).collection[projectPrefix + 'pnfd:pnfd'];
             // };
             resolve({
                 statusCode: response.statusCode || 200,
@@ -315,10 +323,10 @@ Catalog.delete = function(req) {
     console.log('Deleting', catalogType, id, 'from', api_server);
     return new Promise(function(resolve, reject) {
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog/' + catalogType + '/' + id,
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog/' + catalogType + '/' + id),
             method: 'DELETE',
             headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             forever: constants.FOREVER_ON,
             rejectUnauthorized: false,
@@ -334,7 +342,7 @@ Catalog.delete = function(req) {
 Catalog.getVNFD = function(req) {
     var api_server = req.query['api_server'];
     var vnfdID = req.body.data;
-    var authorization = req.get('Authorization');
+    var authorization = req.session && req.session.authorization;
     var VNFDs = [];
     if (typeof(vnfdID) == "object" && vnfdID.constructor.name == "Array") {
         vnfdID.map(function(id) {
@@ -363,7 +371,7 @@ Catalog.getVNFD = function(req) {
         return new Promise(function(resolve, reject) {
             var url = utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd' + (id ? '/' + id : '') + '?deep';
             request({
-                uri: url,
+                uri: utils.projectContextUrl(req, url),
                 method: 'GET',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
                     'Authorization': authorization
@@ -399,10 +407,10 @@ Catalog.create = function(req) {
     return new Promise(function(resolve, reject) {
         var requestHeaders = {};
         _.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         });
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog'),
             method: 'POST',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -429,10 +437,10 @@ Catalog.update = function(req) {
     return new Promise(function(resolve, reject) {
         var requestHeaders = {};
         _.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         });
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog' + '/' + catalogType + '/' + id,
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog' + '/' + catalogType + '/' + id),
             method: 'PUT',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -508,12 +516,13 @@ NSR.get = function(req) {
     var nsrPromises = [];
     var api_server = req.query["api_server"];
     var id = req.params.id;
+    var projectPrefix = req.session.projectId ? "project-" : "";
     var nsdInfo = new Promise(function(resolve, reject) {
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep'),
             method: 'GET',
             headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             forever: constants.FOREVER_ON,
             rejectUnauthorized: false,
@@ -523,7 +532,7 @@ NSR.get = function(req) {
                 var isString = typeof(response.body) == "string";
                 if (isString && response.body == '') return resolve('empty');
                 data = isString ? JSON.parse(response.body) : response.body;
-                var nsdData = data.collection["nsd:nsd"];
+                var nsdData = data.collection[projectPrefix + "nsd:nsd"];
                 if (nsdData.constructor.name == "Object") {
                     nsdData = [nsdData];
                 }
@@ -533,10 +542,10 @@ NSR.get = function(req) {
     })
     var config = new Promise(function(resolve, reject) {
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-config/nsr' + (id ? '/' + id : '') + '?deep',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-config/nsr' + (id ? '/' + id : '') + '?deep'),
             method: 'GET',
             headers: _.extend({}, id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             forever: constants.FOREVER_ON,
             rejectUnauthorized: false,
@@ -557,10 +566,10 @@ NSR.get = function(req) {
     });
     var opData = new Promise(function(resolve, reject) {
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata/nsr' + (id ? '/' + id : '') + '?deep',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata/nsr' + (id ? '/' + id : '') + '?deep'),
             method: 'GET',
             headers: _.extend({}, id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             forever: constants.FOREVER_ON,
             rejectUnauthorized: false,
@@ -866,10 +875,10 @@ NSR.create = function(req) {
     return new Promise(function(resolve, reject) {
         var requestHeaders = {};
         _.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         });
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config'),
             method: 'POST',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -910,10 +919,10 @@ NSR.delete = function(req) {
     return new Promise(function(resolve, reject) {
         var requestHeaders = {};
         _.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         });
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id,
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id),
             method: 'DELETE',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1006,10 +1015,10 @@ NSR.setStatus = function(req) {
         }
         var requestHeaders = {};
         _.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         });
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id + '/admin-status/',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id + '/admin-status/'),
             method: 'PUT',
             headers: requestHeaders,
             json: {
@@ -1059,12 +1068,12 @@ NSR.createScalingGroupInstance = function(req) {
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data,
             {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }
         );
 
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id + '/scaling-group/' + scaling_group_id + '/instance',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id + '/scaling-group/' + scaling_group_id + '/instance'),
             method: 'POST',
             headers: requestHeaders,
             json: jsonData,
@@ -1108,12 +1117,12 @@ NSR.deleteScalingGroupInstance = function(req) {
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data,
             {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }
         );
 
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id + '/scaling-group/' + scaling_group_id + '/instance/' + scaling_instance_id,
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id + '/scaling-group/' + scaling_group_id + '/instance/' + scaling_instance_id),
             method: 'DELETE',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1152,12 +1161,12 @@ NSR.nsd.vld.get = function(req) {
         _.extend(requestHeaders,
             vld_id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection,
             {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }
         );
 
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + nsr_id + '/nsd/vld' + (vld_id ? '/' + vld_id : '')  +'?deep',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + nsr_id + '/nsd/vld' + (vld_id ? '/' + vld_id : '')  +'?deep'),
             method: 'GET',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1197,10 +1206,10 @@ NSR.nsd.vld.create = function(req) {
     return new Promise(function(resolve, reject) {
         var requestHeaders = {};
         _.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
-            'Authorization': req.get('Authorization')
+            'Authorization': req.session && req.session.authorization
         });
         request({
-            uri: utils.confdPort(api_server) + '/api/config/ns-instance-config/nsr/' + nsr_id + '/nsd/vld' + (vld_id ? '/' + vld_id : ''),
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/ns-instance-config/nsr/' + nsr_id + '/nsd/vld' + (vld_id ? '/' + vld_id : '')),
             method: vld_id ? 'PUT' : 'POST',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1239,12 +1248,12 @@ NSR.nsd.vld.delete = function(req) {
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data,
             {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }
         );
 
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + nsr_id + '/nsd/vld/' + vld_id,
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + nsr_id + '/nsd/vld/' + vld_id),
             method: 'DELETE',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1266,11 +1275,11 @@ VNFR.get = function(req) {
     var uri = utils.confdPort(api_server);
     uri += APIVersion + '/api/operational/vnfr-catalog/vnfr' + (id ? '/' + id : '') + '?deep';
     var headers = _.extend({}, id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
     });
     return new Promise(function(resolve, reject) {
         request({
-            url: uri,
+            url: utils.projectContextUrl(req, uri),
             method: 'GET',
             headers: headers,
             forever: constants.FOREVER_ON,
@@ -1278,10 +1287,10 @@ VNFR.get = function(req) {
         }, function(error, response, body) {
             if (utils.validateResponse('VNFR.get', error, response, body, resolve, reject)) {
                 var data = JSON.parse(response.body);
-                var returnData = id ? [data["vnfr:vnfr"]] : data.collection["vnfr:vnfr"];
+                var returnData = id ? (data["vnfr:vnfr"] ? [data["vnfr:vnfr"]] : []) : data.collection["vnfr:vnfr"];
                 returnData.forEach(function(vnfr) {
-                    vnfr['nfvi-metrics'] = buildNfviGraphs(vnfr.vdur);
-                    vnfr['epa-params'] = epa_aggregator(vnfr.vdur);
+                    vnfr['nfvi-metrics'] = vnfr.vdur ? buildNfviGraphs(vnfr.vdur) : [];
+                    vnfr['epa-params'] = vnfr.vdur ? epa_aggregator(vnfr.vdur) : [];
                     vnfr['service-primitives-present'] = (vnfr['vnf-configuration'] && vnfr['vnf-configuration']['service-primitive'] && vnfr['vnf-configuration']['service-primitive'].length > 0) ? true : false;
                     vnfr['vdur'] && vnfr['vdur'].map(function(vdur, vdurIndex) {
                         // This console-url is what front-end will hit to generate a real console-url
@@ -1341,7 +1350,7 @@ VNFR.getByNSR = function(req) {
     delete reqClone.params.id;
     uri += APIVersion + '/api/operational/ns-instance-opdata/nsr/' + id + '?deep';
     var headers = _.extend({}, id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
     });
     return new Promise(function(resolve, reject) {
         if (VNFR.cachedNSR[id]) {
@@ -1352,7 +1361,7 @@ VNFR.getByNSR = function(req) {
             });
         } else {
             request({
-                url: uri,
+                url: utils.projectContextUrl(req, uri),
                 method: 'GET',
                 headers: headers,
                 forever: constants.FOREVER_ON,
@@ -1388,11 +1397,11 @@ VLR.get = function(req) {
     var uri = utils.confdPort(api_server);
     uri += APIVersion + '/api/operational/vlr-catalog/vlr' + (id ? '/' + id : '') + '?deep';
     var headers = _.extend({}, id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
     });
     return new Promise(function(resolve, reject) {
         request({
-            url: uri,
+            url: utils.projectContextUrl(req, uri),
             method: 'GET',
             headers: headers,
             forever: constants.FOREVER_ON,
@@ -1416,10 +1425,10 @@ RIFT.api = function(req) {
     var url = req.path;
     return new Promise(function(resolve, reject) {
         request({
-            url: uri + url + '?deep',
+            url: utils.projectContextUrl(req, uri + url + '?deep'),
             method: 'GET',
             headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             forever: constants.FOREVER_ON,
             rejectUnauthorized: false,
@@ -1444,11 +1453,11 @@ ComputeTopology.get = function(req) {
     return new Promise(function(resolve, reject) {
         var nsrPromise = new Promise(function(success, failure) {
             request({
-                uri: utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata/nsr/' + nsr_id + '?deep',
+                uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata/nsr/' + nsr_id + '?deep'),
                 method: 'GET',
                 headers: _.extend({},
                     constants.HTTP_HEADERS.accept.data, {
-                        'Authorization': req.get('Authorization')
+                        'Authorization': req.session && req.session.authorization
                     }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
@@ -1493,10 +1502,10 @@ ComputeTopology.get = function(req) {
                     vnfrPromises.push(
                         new Promise(function(success, failure) {
                             rp({
-                                uri: utils.confdPort(api_server) + APIVersion + '/api/operational/vnfr-catalog/vnfr/' + vnfrId + '?deep',
+                                uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/vnfr-catalog/vnfr/' + vnfrId + '?deep'),
                                 method: 'GET',
                                 headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                                    'Authorization': req.get('Authorization')
+                                    'Authorization': req.session && req.session.authorization
                                 }),
                                 forever: constants.FOREVER_ON,
                                 rejectUnauthorized: false,
@@ -1587,11 +1596,11 @@ NetworkTopology.get = function(req) {
     var uri = utils.confdPort(api_server);
     uri += APIVersion + '/api/operational/network?deep';
     var headers = _.extend({}, constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
     });
     return new Promise(function(resolve, reject) {
         request({
-            url: uri,
+            url: utils.projectContextUrl(req, uri),
             method: 'GET',
             headers: headers,
             forever: constants.FOREVER_ON,
@@ -1618,11 +1627,11 @@ VDUR.get = function(req) {
     var uri = utils.confdPort(api_server);
     uri += APIVersion + '/api/operational/vnfr-catalog/vnfr/' + vnfrID + '/vdur/' + vdurID + '?deep';
     var headers = _.extend({}, constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
     });
     return new Promise(function(resolve, reject) {
         request({
-            url: uri,
+            url: utils.projectContextUrl(req, uri),
             method: 'GET',
             headers: headers,
             forever: constants.FOREVER_ON,
@@ -1645,11 +1654,11 @@ VDUR.consoleUrl.get = function(req) {
     var uri = utils.confdPort(api_server);
     uri += APIVersion + '/api/operational/vnfr-console/vnfr/' + vnfrID + '/vdur/' + vdurID + '/console-url' + '?deep';
     var headers = _.extend({}, constants.HTTP_HEADERS.accept.data, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
     });
     return new Promise(function(resolve, reject) {
         request({
-            url: uri,
+            url: utils.projectContextUrl(req, uri),
             method: 'GET',
             headers: headers,
             forever: constants.FOREVER_ON,
@@ -1672,11 +1681,11 @@ CloudAccount.get = function(req) {
     var uri = utils.confdPort(api_server);
     uri += APIVersion + '/api/operational/cloud/account?deep';
     var headers = _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-        'Authorization': req.get('Authorization')
+        'Authorization': req.session && req.session.authorization
     });
     return new Promise(function(resolve, reject) {
         request({
-            url: uri,
+            url: utils.projectContextUrl(req, uri),
             method: 'GET',
             headers: headers,
             forever: constants.FOREVER_ON,
@@ -1709,11 +1718,11 @@ ConfigAgentAccount.get = function(req) {
             var requestHeaders = {};
             _.extend(requestHeaders,
                 constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 });
 
             request({
-                    url: utils.confdPort(api_server) + APIVersion + '/api/operational/config-agent/account',
+                    url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/config-agent/account'),
                     type: 'GET',
                     headers: requestHeaders,
                     forever: constants.FOREVER_ON,
@@ -1749,11 +1758,11 @@ ConfigAgentAccount.get = function(req) {
             var requestHeaders = {};
             _.extend(requestHeaders,
                 constants.HTTP_HEADERS.accept.data, {
-                    'Authorization': req.get('Authorization')
+                    'Authorization': req.session && req.session.authorization
                 });
 
             request({
-                    url: utils.confdPort(api_server) + APIVersion + '/api/operational/config-agent/account/' + id,
+                    url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/config-agent/account/' + id),
                     type: 'GET',
                     headers: requestHeaders,
                     forever: constants.FOREVER_ON,
@@ -1802,11 +1811,11 @@ ConfigAgentAccount.create = function(req) {
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
 
         request({
-            url: utils.confdPort(api_server) + APIVersion + '/api/config/config-agent',
+            url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/config-agent'),
             method: 'POST',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1841,11 +1850,11 @@ ConfigAgentAccount.update = function(req) {
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data,
             constants.HTTP_HEADERS.content_type.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
 
         request({
-            url: utils.confdPort(api_server) + APIVersion + '/api/config/config-agent/account/' + id,
+            url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/config-agent/account/' + id),
             method: 'PUT',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1883,10 +1892,10 @@ ConfigAgentAccount.delete = function(req) {
         var requestHeaders = {};
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
         request({
-            url: utils.confdPort(api_server) + APIVersion + '/api/config/config-agent/account/' + id,
+            url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/config-agent/account/' + id),
             method: 'DELETE',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1909,10 +1918,10 @@ DataCenters.get = function(req) {
         var requestHeaders = {};
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
         request({
-            url: utils.confdPort(api_server) + APIVersion + '/api/operational/datacenters?deep',
+            url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/datacenters?deep'),
             method: 'GET',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1950,10 +1959,10 @@ SSHkey.get  = function(req) {
         var requestHeaders = {};
         _.extend(requestHeaders,
             constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             });
         request({
-            url: utils.confdPort(api_server) + APIVersion + '/api/config/key-pair?deep',
+            url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/key-pair?deep'),
             method: 'GET',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1987,10 +1996,10 @@ SSHkey.delete = function(req) {
     console.log('Deleting ssk-key', id);
     return new Promise(function(resolve, reject) {
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/' + id,
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/' + id),
             method: 'DELETE',
             headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             forever: constants.FOREVER_ON,
             rejectUnauthorized: false,
@@ -2008,10 +2017,10 @@ SSHkey.post = function(req) {
     var data = req.body;
     return new Promise(function(resolve, reject) {
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/'),
             method: 'POST',
             headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             json: data,
             forever: constants.FOREVER_ON,
@@ -2031,10 +2040,10 @@ SSHkey.put = function(req) {
     var data = req.body;
     return new Promise(function(resolve, reject) {
         request({
-            uri: utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/',
+            uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/'),
             method: 'PUT',
             headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
-                'Authorization': req.get('Authorization')
+                'Authorization': req.session && req.session.authorization
             }),
             json: data,
             forever: constants.FOREVER_ON,
index 851239f..1f4d145 100644 (file)
@@ -4,6 +4,7 @@
     "dashboard": "./launchpad.jsx",
     "order": 1,
     "priority":1,
+    "allow": ["rw-rbac-platform:super-admin", "rw-project-mano:lcm-oper", "rw-project-mano:lcm-admin"],
     "routes": [
     {
         "label": "Dashboard",
index f6471ab..5353a8d 100644 (file)
     "normalizr": "^2.1.0",
     "open-iconic": "^1.1.1",
     "prismjs": "^1.4.1",
-    "react": "^0.14.8",
     "react-awesome-modal": "^0.3.3",
     "react-breadcrumbs": "^1.3.9",
     "react-crouton": "^0.2.7",
-    "react-dom": "^0.14.6",
     "react-router": "^2.0.1",
     "react-slick": "^0.11.1",
     "react-tabs": "^0.8.0",
     "react-treeview": "^0.4.2",
+    "react": "^0.14.8",
+    "react-dom": "^0.14.6",
     "request-promise": "^3.0.0",
     "underscore": "^1.8.3"
   },
index 7bed778..aba84c5 100644 (file)
@@ -24,10 +24,15 @@ import InstantiateSelectDescriptorPanel from './instantiateSelectDescriptorPanel
 import CatalogDescriptorRaw from './catalogDescriptorRaw.jsx'
 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
 import {Panel, PanelWrapper} from 'widgets/panel/panel';
-import Button from 'widgets/button/rw.button.js'
+import Button from 'widgets/button/rw.button.js';
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
 import 'style/layout.scss';
 import './instantiateDashboard.scss';
 
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
 class InstantiateDashboard extends React.Component {
     constructor(props) {
         super(props);
@@ -107,6 +112,7 @@ class InstantiateDashboard extends React.Component {
         let html;
         let selectedNSDid = self.state.selectedNSDid;
         let isPreviewing = self.state.isPreviewing;
+        const hasAccess = isRBACValid(this.context.userProfile, [PROJECT_ROLES.LCM_ADMIN]);
         let descriptorPreview = (
             <Panel title={(self.state.selectedNSD['short-name'] || self.state.selectedNSD.name ) + ' Descriptor Preview'} className="CatalogDescriptorPreview">
             <span className="oi CatalogDescriptorPreview-button" data-glyph={"circle-x"} onClick={self.Store.deselectDescriptor}></span>
@@ -136,10 +142,11 @@ class InstantiateDashboard extends React.Component {
 
                     <Button label="Cancel" onClick={this.handleCancel}/>
                     {this.isSelectPage() ?
-                        <Button label="Next" isLoading={this.state.isSaving} onClick={this.state.selectedNSD && self.openDescriptor} className="dark"  type="submit"/>
+                        <Button label="Next" isLoading={this.state.isSaving} onClick={this.state.selectedNSD && self.openDescriptor} className="dark" type="submit"/>
                         : <div>
                             <Button label="Back" onClick={this.handleBack}/>
-                            <Button label="Launch" isLoading={this.state.isSaving} onClick={self.handleSave.bind(self, true)} className="dark"  type="submit"/>
+                            { hasAccess ? <Button label="Launch" isLoading={this.state.isSaving} onClick={self.handleSave.bind(self, true)} className="dark"  type="submit"
+                            /> : null}
                         </div>
                     }
                 </div>
@@ -148,6 +155,7 @@ class InstantiateDashboard extends React.Component {
     }
 }
 InstantiateDashboard.contextTypes = {
-    router: React.PropTypes.object
+    router: React.PropTypes.object,
+    userProfile: React.PropTypes.object
 };
 export default SkyquakeComponent(InstantiateDashboard);
diff --git a/skyquake/plugins/launchpad/src/launchpad-create.js b/skyquake/plugins/launchpad/src/launchpad-create.js
deleted file mode 100644 (file)
index cbe6d4b..0000000
+++ /dev/null
@@ -1,132 +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.
- *
- */
-require('../components/form-controls.js');
-require('./launchpad-launch-fleet.html');
-var createStore = require('./createStore.js');
-var createActions = require('./createActions.js');
-
-
-angular.module('launchpad')
-    .controller('launchpadCreateCtrl', function($timeout, $stateParams, $state) {
-        var self = this;
-        self.createStore =
-        // var createChannel = $rw.radio.channel('createFleet');
-        var apiServer = self.isOnline = require('utils/rw.js').getSearchParams(window.location).api_server;
-        // var federationChannel = $rw.radio.channel('federationChannel');
-        self.fleet = {
-            template_id: null,
-            pool_id: null,
-            description: '',
-            epa_attributes: {},
-            status: "active",
-            name: 'NEW FLEET'
-        };
-        self.slaParams = [];
-        self.federation = $stateParams.id;
-        createStore.getNetworkServices();
-        createStore.getSlaParams();
-        createStore.getPools();
-        createStore.listen(function(state) {
-                $timeout(function() {
-                    self.networkServices = state.networkServices;
-                    self.slaParams = state.slaParams;
-                    self.fleet.pool = state.pools[0];
-                        self.pools = state.pools;
-                    angular.forEach(self.slaParams, function(v) {
-                        if (!v.hasOwnProperty('value')) {
-                            v.value = v.options.second;
-                        };
-                        return v;
-                    });
-                })
-            })
-            // federationChannel.request("federation:services").then(function(data) {
-            //     $timeout(function() {
-            //         // self.fleet.service = 'cag';
-            //         self.networkServices = data;
-            //         createChannel.request('vnfParams', 'cag').then(function(data) {
-            //         $timeout(function() {
-            //             self.vnfParams = data;
-            //         });
-            //     });
-            //     });
-            // });
-            //     federationChannel.request('federation:pools', apiServer).then(function(data) {
-            //         $timeout(function() {
-            //             console.log('pools:', data)
-            //             self.fleet.pool = data[0];
-            //             self.pools = data;
-            //         })
-            //     });
-
-        //         federationChannel.request('federation:sla-params').then(function(data) {
-        //             $timeout(function() {
-        //                 self.slaParams = data;
-        //                 angular.forEach(self.slaParams, function(v) {
-        //                     if (!v.hasOwnProperty('value')) {
-        //                         v.value = v.options.second;
-        //                     };
-        //                     return v;
-        //                 });
-        //             }
-        //             );
-        //         });
-
-        // federationChannel.on("launchpadCreate", function() {
-        //         $state.go('launchpad', null, {reload: false});
-
-        // });
-
-        self.generateServiceImage = function(service) {
-            return ('assets/img/svg/' + service.src + (self.isSelectedService(service.id) ? '-active' : '-inactive') + '.svg');
-        };
-        self.generatePoolImage = function(pool) {
-            return ('assets/img/svg/' + self.refsDB.resources.openstackCloud.pools[pool.ref].src + (self.isSelectedPool(pool) ? '-active' : '-inactive') + '.svg');
-        };
-        self.isSelectedPool = function(id) {
-            return id == self.fleet.pool_id;
-        };
-        self.isSelectedService = function(id) {
-            return id == self.fleet.template_id;
-        };
-        self.launch = function(launch) {
-            if (self.fleet.name == "") {
-                createActions.validateError('Plase Name the Service')
-            }
-            createActions.validateReset();
-            self.slaParams.forEach(function(v) {
-                if (v.value.indexOf("RRC") > -1) {
-                    v.value = "RRC";
-                }
-                self.fleet.epa_attributes[v.ref] = v.value;
-            });
-            delete self.fleet.pool;
-            self.fleet.status = launch ? 'active' : 'inactive';
-            createStore.createEnvironment(self.fleet)
-        };
-        self.selectPool = function(id) {
-            self.fleet.pool_id = id;
-            // createChannel.command("pool:select", id);
-        };
-        self.selectService = function(id) {
-
-            self.fleet.template_id = id;
-            // createChannel.command("service:select", id);
-        };
-    });
index 759c7d6..88dd1bb 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,6 +26,12 @@ import NsListPanel from './nsListPanel/nsListPanel.jsx';
 import Crouton from 'react-crouton'
 import AppHeader from 'widgets/header/header.jsx';
 import './launchpad.scss';
+
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+
+const PROJECT_ROLES = ROLES.PROJECT;
+
 let ReactCSSTransitionGroup = require('react-addons-css-transition-group');
 var LaunchpadFleetActions = require('./launchpadFleetActions.js');
 var LaunchpadFleetStore = require('./launchpadFleetStore.js');
@@ -90,6 +96,7 @@ export default class LaunchpadApp extends React.Component {
 
   render () {
     var self = this;
+    const hasAccess = isRBACValid(this.context.userProfile, [PROJECT_ROLES.LCM_ADMIN]);
     let mgmtDomainName = window.location.hash.split('/')[2];
     let navItems = [];
     if(!mgmtDomainName) {
@@ -110,14 +117,16 @@ export default class LaunchpadApp extends React.Component {
             isVisible={self.state.isNsListPanelVisible}
             />
           <NsCardPanel nsrs={self.state.nsrs}
-            openedNsrIDs={self.state.openedNsrIDs} />
+            openedNsrIDs={self.state.openedNsrIDs}
+            hasAccess={hasAccess} />
         </div>
       </div>
     );
     }
 }
 LaunchpadApp.contextTypes = {
-    router: React.PropTypes.object
+    router: React.PropTypes.object,
+    userProfile: React.PropTypes.object
 };
 LaunchpadApp.defaultProps = {
   // name: 'Loading...',
diff --git a/skyquake/plugins/launchpad/src/launchpad_card/launchpad-card.js b/skyquake/plugins/launchpad/src/launchpad_card/launchpad-card.js
deleted file mode 100644 (file)
index de27638..0000000
+++ /dev/null
@@ -1,370 +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.
- *
- */
-require('./launchpad-fleet-card-params.html');
-var React = require('react');
-var LaunchpadCard = require('./launchpadCard.jsx');
-// var LaunchpadCard = require('../../components/dashboard_card/dashboard_card.jsx');
-angular.module('launchpad')
-  .directive('fleetCard', function($state, $stateParams) {
-    return {
-      restrict: 'AE',
-      replace: true,
-      template: '<div></div>',
-      scope: {
-        fleet: '=data',
-        $index: '=',
-        metric: '=?',
-        slaParam: '=?',
-        nfviMetric: '=?'
-      },
-
-      bindToController: true,
-      controllerAs: 'card',
-      link: function(scope, element, attributes) {
-
-      },
-      controller: function($timeout, $interval, $scope, $rootScope, $element) {
-        var self = this;
-
-        console.log(self.fleet)
-         $scope.$watch(function() {
-          return self.fleet}, reactRender)
-        function reactRender() {
-          console.log('rendering', self.fleet)
-          React.render(
-            React.createElement(LaunchpadCard, {className:'launchpadCard'}
-            ),
-           $element[0]
-          );
-        }
-        //FOR WAG ONLY
-        //   self.isOn = false;
-
-        //   //END WAG ONLY
-        //   //Remove for testing only
-        //   $scope.$watch(function() {
-        //     return self.fleet;
-        //   }, function() {});
-
-        //   // var fleetChannel = $rw.radio.channel('fleetChannel');
-        //   var FleetStore = require('../launchpadFleetStore.js');
-        //   self.valueFormat = {
-        //     "int": 1,
-        //     "dec": 0
-        //   };
-        //   self.federationID = $stateParams.id;
-        //   //if this is true, set gauges to start
-        //   if (require('utils/rw.js').getSearchParams(window.location).api_server) {
-        //     self.fleet.started = true;
-        //   }
-        //   self.apiServer = require('utils/rw.js').getSearchParams(window.location).api_server;
-        //   self.isNoisy = false;
-        //   var rateChanged = function(rate) {
-        //     self.rate = rate;
-        //     fleetChannel.command('fleet:change:rate', rate)
-        //   }
-        //   var packetSizeChanged = function(packetSize) {
-        //     self.packetSize = packetSize
-        //     fleetChannel.command('fleet:change:packetSize', packetSize)
-        //   };
-        //   self.openFleet = function(index) {
-        //     var params = require('utils/rw.js').getSearchParams(window.location);
-        //     if (params.api_server) {
-        //       if (params.api_server == "http://10.0.201.25:5000") {
-        //         var newLoc = window.location.origin + window.location.pathname + '?config=' + params.config + '&api_server=' + 'http://10.0.201.25:5050' + '#/wag';
-        //         window.open(newLoc);
-        //         return;
-        //       }
-        //       if (self.fleet.api == "10.0.117.22") self.fleet.api = "10.0.117.17";
-        //       var newLoc = window.location.origin + window.location.pathname + '?config=' + params.config + '&api_server=' + 'http://' + self.fleet.api + ':5050' + '&name=' + self.fleet.name + '&env_id=' + self.fleet.id + '#/dashboard/' + $stateParams.id + '/' + index;;
-        //       console.log('Opening new window at: %s', newLoc)
-        //       window.open(newLoc);
-        //     } else {
-        //       if (self.fleet.id == 'wag-fleet') {
-        //         var newLoc = window.location.origin + window.location.pathname + '?config=' + params.config + '#/wag';
-        //         window.open(newLoc);
-        //       }
-        //       window.open('#/dashboard/' + $stateParams.id + '/' + index);
-        //     }
-        //   };
-        //   self.openConsole = function(index) {
-        //     console.log(self);
-        //     var params = require('utils/rw.js').getSearchParams(window.location);
-        //     if (params.api_server) {
-        //       if (self.fleet.api == "10.0.117.22") self.fleet.api = "10.0.117.17";
-        //       var newLoc = self.apiServer + '/api/environments/' + self.fleet.id + '/console';
-        //       window.open(newLoc);
-        //     } else {
-        //       window.open('#/dashboard/' + $stateParams.id + '/' + index);
-        //     }
-        //   };
-
-
-        //   self.noisyToggle = function() {
-        //     var action;
-        //     if (self.isNoisy) {
-        //       action = 'stop';
-        //       self.isNoisy = false;
-        //     } else {
-        //       action = 'start';
-        //       self.isNoisy = true;
-        //     }
-        //     $.ajax({
-        //       url: "http://" + self.fleet.api + ':5050/api/operations/' + action + '-stream',
-        //       type: "POST",
-        //       headers: {
-        //         "Content-Type": "application/vnd.yang.operation+json"
-        //       },
-        //       dataType: "json",
-        //       data: JSON.stringify({
-        //         "input": {
-        //           "now": ""
-        //         }
-        //       })
-        //     });
-        //   };
-
-        //   self.toggleStatus = function() {
-        //     var state;
-        //     var status = self.fleet.status;
-        //     console.log(self.fleet)
-        //     if (status == "starting" || status == "stopping") {
-        //       return;
-        //     }
-        //     if (status == "active") {
-        //       state = "inactive";
-        //     } else {
-        //       state = "active";
-        //     }
-        //     self.fleet.status = state;
-        //     FleetStore.setFleetState(self.fleet.id, state);
-        //   };
-        //   //CAT + NOISY NEIGHBOR ONLY
-
-        //   self.catStarted = false;
-        //   self.catToggle = function() {
-        //     var action;
-        //     if (self.catStarted) {
-        //       action = 'stop';
-        //       self.catStarted = false;
-        //     } else {
-        //       action = 'start';
-        //       self.catStarted = true;
-        //     }
-        //     $.ajax({
-        //       url: "http://" + self.fleet.api + ':5050/api/operations/' + action + '-cat',
-        //       type: "POST",
-        //       headers: {
-        //         "Content-Type": "application/vnd.yang.operation+json"
-        //       },
-        //       dataType: "json",
-        //       data: JSON.stringify({
-        //         "input": {
-        //           "now": ""
-        //         }
-        //       })
-        //     });
-        //   };
-
-        //   self.deleteFleet = function() {
-        //     if (confirm("Do you really want to delete this fleet?")) {
-        //       // if (confirm("Seriously, you REALLY want to delete this?")) {
-        //         // fleetChannel.request('fleet:delete', self.fleet);
-        //         FleetStore.deleteFleet(self.fleet.id)
-        //       // }
-        //     }
-        //   };
-
-
-        //   /**
-        //    *  WAG Code Start
-        //    **/
-        //   // var wagChannel = $rw.radio.channel('wag');
-
-        //   //If Wag page Offline
-        //   if (!self.apiServer && self.fleet.id == 'wag-fleet') {
-        //     self.offline = setInterval(function() {
-        //       if (self.fleet.started) {
-        //         self.fleet.wagpage.clientsim['traffic-gen']['rx-pps'] = 100 * (Math.random() - .5) + 200;
-        //         self.fleet.wagpage.clientsim['traffic-gen']['tx-pps'] = 100 * (Math.random() - .5) + 200;
-        //       }
-        //     }, 2000);
-        //   }
-
-        //   // If Wag page Online
-        //   if (self.fleet.template_name == 'Wireless Access Gateway') {
-        //     wagChannel.command('wag-poll');
-        //     wagChannel.on('wag-update', function(data) {
-        //       $timeout(function() {
-        //         self.fleet.wagpage = {};
-        //         self.fleet.wagpage.clientsim = data.clientsim
-        //           // self.data.clientsim['traffic-sink']['rx-pps'] / 1000000;
-        //         self.isOn = data.clientsim['traffic-gen-status'].running;
-        //       });
-        //     });
-        //   }
-
-        //   $rootScope.$on('$stateChangeStart', function() {
-        //     wagChannel.command('wag-poll:kill');
-        //   });
-
-        //   /**
-        //    *  WAG Code End
-        //    **/
-
-
-      }
-    };
-  })
-
-.directive('launchpadFleetCardParams', function() {
-  return {
-    restrict: 'AE',
-    replace: true,
-    templateUrl: '/modules/launchpad/launchpad_card/launchpad-fleet-card-params.html',
-    controllerAs: 'params',
-    scope: {
-      $index: "=",
-      cardData: '=',
-      slaParam: '=?',
-      nfviMetric: '=?'
-    },
-    bindToController: true,
-    controller: function($scope, $stateParams, $timeout, $interval, $rootScope) {
-      //remove watch
-      $scope.$watch(function() {
-        return self.cardData;
-      }, function() {})
-      var self = this;
-      var apiServer = require('utils/rw.js').getSearchParams(window.location).api_server;
-      self.apiServer = apiServer;
-      // self.refs = refsDB;
-
-
-      // var LaunchpadFleetActions = require('../launchpadFleetActions.js');
-      // var LaunchpadFleetStore = require('../launchpadFleetStore.js');
-      //var LaunchpadFleetActions = require('./launchpadFleetActions.js');
-
-      self.rate = 40;
-      self.packetSize = 256;
-      self.refParam = $rw.refParams;
-      var currentNFVIRef;
-
-      this.visible = 'default';
-      if (self.cardData.id == "wag-fleet") {
-        self.cardData.started = self.cardData.wagpage.clientsim['traffic-gen-status'].running;
-      }
-
-
-      self.selectedNFVI = 0;
-      self.selectedSLAParam = {};
-      self.refPools = $rw.refPools;
-
-      // Event Listeners
-      // View Actions
-      self.load = function(panel) {
-        self.visible = panel;
-      };
-      self.filterSLA = function(ref) {
-        // fleetChannel.command('filter:SLAParams', ref);
-        FleetActions.filterSLAParams(ref);
-      };
-      self.filterNFVI = function(ref) {
-        FleetActions.filterNfviMetrics(ref)
-          // fleetChannel.command('filter:NFVIMetrics', ref);
-      };
-      self.serviceToggle = function(fleet, control) {
-        if (apiServer) {
-          var vnfrChannel = $rw.radio.channel('vnfr');
-          switch (control.ref) {
-            case "trafgen":
-              var action = (control.started) ? 'stop' : 'start';
-              vnfrChannel.command("vnf:command", action, control.data.api, "http://" + fleet.api + ':5050');
-              break;
-            case "iot_army":
-              var action = (control.started) ? 'stop' : 'start';
-              vnfrChannel.command("vnf:command", action, control.data.api, "http://" + fleet.api + ':5050');
-              break;
-          }
-        } else {
-          fleetChannel.command('fleet:start', $stateParams.id, self.$index);
-          if ($stateParams.id == 'wag-federation') {
-            if (!self.cardData.wagpage.clientsim['traffic-gen-status']) {
-              self.cardData.wagpage.clientsim['traffic-gen-status'] = {};
-            }
-            self.cardData.wagpage.clientsim['traffic-gen-status'].running = !self.cardData.wagpage.clientsim['traffic-gen-status'].running;
-          }
-        }
-
-      };
-      // Init Params
-
-
-      //WAG ONLY
-      if (self.apiServer == "http://10.0.201.25:5000") {
-        self.isWag = true;
-      }
-      self.toggleOn = function(e) {
-        // self.setPacket(e, self.packetSize);
-        // self.setRate(e, self.rate);
-        // self.setSubscribers(e, self.subscribers);
-        // self.setAP(e, self.ap);
-        var wagServer = ""
-        if (self.apiServer) {
-          if (self.isOn) {
-            $.ajax({
-              type: "POST",
-              url: "http://10.0.201.25:5050/api/running/stop-device-group/",
-              success: (function() {
-                console.log('stahp post sent')
-              })
-            });
-            $.ajax({
-              type: "POST",
-              url: "http://10.0.201.25:5050/api/running/stop-sink-server/",
-              success: (function() {
-                console.log('stahp post sent')
-              })
-            });
-          } else {
-            $.ajax({
-              type: "POST",
-              url: "http://10.0.201.25:5050/api/running/start-sink-server/",
-              success: (function() {
-                console.log('start post sent')
-              })
-            });
-            $.ajax({
-              type: "POST",
-              url: "http://10.0.201.25:5050/api/running/start-device-group/",
-              success: (function() {
-                console.log('start post sent')
-              })
-            });
-          }
-        }
-        self.isOn = !self.isOn;
-      }
-
-      //END WAG ONLY
-
-    },
-    link: function(s, e, a) {}
-  }
-});
index e68a659..b4ebb81 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -98,7 +98,9 @@ class LaunchpadCard extends React.Component {
               <img src={circleXImage} title="Close card" />
               </a>
       );
-
+      if (this.props.hasAccess) {
+        closeButton = null;
+      }
       html = (
         <DashboardCard className={'launchpadCard'} closeCard={closeButton}>
           <LaunchpadHeader nsr={this.props.nsr} name={this.props.name} isActive={this.props.isActive} id={this.props.id}/>
index 1856dda..79a587b 100644 (file)
@@ -36,7 +36,7 @@ export default class LaunchpadCardCloudAccount extends React.Component {
         (<li key="nsr"><h3>NSR: {this.props.nsr['cloud-account']}</h3></li>)
       )
     }
-    this.props.nsr['vnfrs'].map(function(v,i) {
+    this.props.nsr && this.props.nsr['vnfrs'] && this.props.nsr['vnfrs'].map(function(v,i) {
       if(v.hasOwnProperty('cloud-account')) {
         status.push(
           (<li key={i}><h3>VNFR {v['short-name']}: {v['cloud-account']}</h3></li>)
index d9b7182..19ccca7 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -61,6 +61,7 @@
     }
 
     constructConfigPrimitiveTabs = (tabList, tabPanels) => {
+        const hasAccess = this.props.hasAccess;
         let mandatoryFieldValue = 'true';
         this.state.vnfrs && this.state.vnfrs.map((vnfr, vnfrIndex) => {
             if (vnfr['vnf-configuration'] && vnfr['vnf-configuration']['service-primitive'] && vnfr['vnf-configuration']['service-primitive'].length > 0) {
                                     {params}
                                 </ul>
                             </div>
-                            <button className="dark" role="button" onClick={this.handleExecuteClick.bind(this, configPrimitiveIndex, vnfrIndex)}>{configPrimitive.name}</button>
+                            {hasAccess ? <button className="dark" role="button" onClick={this.handleExecuteClick.bind(this, configPrimitiveIndex, vnfrIndex)}>{configPrimitive.name}</button> : null}
                         </TabPanel>
                     )
                 });
     }
 
     render() {
-
         let tabList = [];
         let tabPanels = [];
         let isConfiguring = (this.props.data['config-status'] && this.props.data['config-status'] != 'configured') || false;
index 2b9b4c1..5d2f536 100644 (file)
@@ -32,7 +32,8 @@ export default class NsCardPanel extends React.Component {
                                 data={nsr.data}
                                 nsr={nsr}
                                 isActive={nsr["admin-status"] == "ENABLED"}
-                                closeButtonAction={this.onCloseCard(nsr.id)}/>
+                                closeButtonAction={this.onCloseCard(nsr.id)}
+                                hasAccess={this.props.hasAccess}/>
                         );
                       }
                     }
index ea3eb2f..7fad6f4 100644 (file)
@@ -36,6 +36,10 @@ import _forEach from 'lodash/forEach';
 import Prism from 'prismjs';
 import 'prismjs/themes/prism.css';
 
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+
+const PROJECT_ROLES = ROLES.PROJECT;
 
 export default class RecordCard extends React.Component {
   constructor(props) {
@@ -77,6 +81,8 @@ export default class RecordCard extends React.Component {
 
     let notice = null;
 
+    let hasAccess = isRBACValid(this.context.userProfile, [PROJECT_ROLES.LCM_ADMIN]);
+
     switch(this.props.type) {
       case 'vnfr' :
         cardData = this.props.data[0];
@@ -86,7 +92,7 @@ export default class RecordCard extends React.Component {
         if (displayConfigPrimitives) {
           configPrimitiveComponent = (
             <div className="flex vnfrConfigPrimitiveContainer">
-              <VnfrConfigPrimitives data={configPrimitivesProps} />
+              <VnfrConfigPrimitives data={configPrimitivesProps} hasAccess={hasAccess} />
             {/* <NsrPrimitiveJobList jobs={cardData['config-agent-job']}/> */}
             <div style={{display:'flex', flexDirection: 'column',     flex: '1 1 40%'}}>
                 <div className="launchpadCard_title">
@@ -419,3 +425,7 @@ RecordCard.defaultProps = {
   isLoading: true,
   jobData: []
 }
+RecordCard.contextTypes = {
+    router: React.PropTypes.object,
+    userProfile: React.PropTypes.object
+};
index 224bad5..05c6e5e 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -89,7 +89,7 @@ export default class RecordView extends React.Component {
 
     let nav = <AppHeader nav={navItems} />
     if (this.state.showRecordDetails) {
-      recordDetails = <RecordDetails isLoading={this.state.detailLoading} data={this.state.rawData} />
+    recordDetails = <RecordDetails isLoading={this.state.detailLoading} data={this.state.rawData} />
     }
     html = (
       <div className="app-body recordView">
index 06c27d8..08103d8 100644 (file)
@@ -43,7 +43,7 @@ var Test = {};
 function buildGetRequestOptions(req, endpoint) {
   var headers = _.extend({},
     constants.HTTP_HEADERS.accept.data, {
-    'Authorization': req.get('Authorization')
+    'Authorization': req.session && req.session.authorization
   });
   var api_server = req.query["api_server"];
   var requestOptions = {
@@ -60,7 +60,7 @@ function buildPutRequestOptions(req, endpoint, jsonData) {
   var headers = _.extend({},
     constants.HTTP_HEADERS.accept.data,
     constants.HTTP_HEADERS.content_type.data, {
-    'Authorization': req.get('Authorization')
+    'Authorization': req.session && req.session.authorization
   });
   var api_server = req.query["api_server"];
   var requestOptions = {
@@ -79,7 +79,7 @@ function buildDeleteRequestOptions(req, endpoint) {
   var headers = _.extend({},
     constants.HTTP_HEADERS.accept.data,
     constants.HTTP_HEADERS.content_type.data, {
-    'Authorization': req.get('Authorization')
+    'Authorization': req.session && req.session.authorization
   });
   var api_server = req.query["api_server"];
   var requestOptions = {
@@ -447,7 +447,7 @@ SysLogViewer.get = function(req) {
       headers: _.extend({},
         constants.HTTP_HEADERS.accept.data,
         {
-          'Authorization': req.get('Authorization')
+          'Authorization': req.session && req.session.authorization
         }),
       forever: foreverOn,
       rejectUnauthorized: false
index 2e2b8dc..301b1eb 100644 (file)
@@ -40,7 +40,8 @@ Support.severities = function() {
  * Class to convert RESTConf data to logging plugin
  */
 
-
+var LoggingConfigDecoder = {};
+var LoggingConfigEncoder = {};
 
 LoggingConfigDecoder = function(debugMode) {
   this.debugMode = debugMode || false
index f68358d..bd6a017 100644 (file)
@@ -2,8 +2,9 @@
     "root": "public",
     "name": "Logging",
     "dashboard": "./loggingGeneral.jsx",
-    "order": 101,
+    "order": 1,
     "priority":2,
+    "admin_link": true,
     "routes": [
         {
             "label": "Logging",
diff --git a/skyquake/plugins/project_management/CMakeLists.txt b/skyquake/plugins/project_management/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f994a55
--- /dev/null
@@ -0,0 +1,38 @@
+# RIFT_IO_STANDARD_CMAKE_COPYRIGHT_HEADER(BEGIN)
+# Author(s): Kiran Kashalkar
+# Creation Date: 08/18/2015
+# RIFT_IO_STANDARD_CMAKE_COPYRIGHT_HEADER(END)
+
+##
+# DEPENDENCY ALERT
+# The submodule dependencies must be specified in the
+# .gitmodules.dep file at the top level (supermodule) directory
+# If this submodule depends other submodules remember to update
+# the .gitmodules.dep
+##
+
+cmake_minimum_required(VERSION 2.8)
+
+##
+# Submodule specific includes will go here,
+# These are specified here, since these variables are accessed
+# from multiple sub directories. If the variable is subdirectory
+# specific it must be declared in the subdirectory.
+##
+
+rift_externalproject_add(
+  project_management
+  DEPENDS skyquake
+  SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
+  CONFIGURE_COMMAND echo
+  BUILD_COMMAND
+    ${CMAKE_CURRENT_BINARY_DIR}/project_management/project_management-build/scripts/build.sh
+  INSTALL_COMMAND
+    ${CMAKE_CURRENT_SOURCE_DIR}/scripts/install.sh
+    ${CMAKE_CURRENT_BINARY_DIR}/project_management/project_management-build
+    ${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+    ${RIFT_SUBMODULE_INSTALL_PREFIX}/skyquake/${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+
+  BCACHE_COMMAND echo
+)
+
diff --git a/skyquake/plugins/project_management/config.json b/skyquake/plugins/project_management/config.json
new file mode 100644 (file)
index 0000000..34537c6
--- /dev/null
@@ -0,0 +1,16 @@
+{
+    "root": "public",
+    "name": "Project Management",
+    "dashboard": "./dashboard/dashboard.jsx",
+    "order": 1,
+    "priority":2,
+    "admin_link": true,
+    "allow": ["rw-rbac-platform:super-admin", "rw-rbac-platform:platform-admin", "rw-rbac-platform:platform-oper", "rw-project:project-admin", "rw-project:project-oper"],
+    "routes": [
+    {
+        "label": "Project Management Dashboard",
+        "route": "project-management",
+        "component": "./dashboard/dashboard.jsx",
+        "type": "internal"
+    }]
+}
diff --git a/skyquake/plugins/project_management/package.json b/skyquake/plugins/project_management/package.json
new file mode 100644 (file)
index 0000000..4ad56af
--- /dev/null
@@ -0,0 +1,54 @@
+{
+  "name": "config",
+  "version": "1.0.0",
+  "description": "",
+  "main": "routes.js",
+  "scripts": {
+    "start": "rm -rf public/ && mkdir public && cd public && mkdir build && cp ../src/index.html ./ && node ../server.js"
+  },
+  "author": "RIFT.io",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "alt": "^0.18.3",
+    "bluebird": "^3.4.1",
+    "express": "^4.13.3",
+    "history": "^1.17.0",
+    "jquery": "^2.2.1",
+    "json-loader": "^0.5.4",
+    "lodash": "^4.10.0",
+    "normalizr": "^2.1.0",
+    "open-iconic": "^1.1.1",
+    "prismjs": "^1.4.1",
+    "react": "^0.14.8",
+    "react-breadcrumbs": "^1.3.9",
+    "react-crouton": "^0.2.7",
+    "react-dom": "^0.14.6",
+    "react-router": "^2.0.1",
+    "react-slick": "^0.11.1",
+    "react-tabs": "^0.5.3",
+    "react-treeview": "^0.4.2",
+    "request-promise": "^3.0.0",
+    "underscore": "^1.8.3"
+  },
+  "devDependencies": {
+    "babel-core": "^6.4.5",
+    "babel-loader": "^6.2.1",
+    "babel-polyfill": "^6.9.1",
+    "babel-preset-es2015": "^6.6.0",
+    "babel-preset-react": "^6.5.0",
+    "babel-preset-stage-0": "^6.3.13",
+    "babel-runtime": "^6.3.19",
+    "cors": "^2.7.1",
+    "css-loader": "^0.23.1",
+    "file-loader": "^0.8.5",
+    "html-webpack-plugin": "^2.9.0",
+    "http-proxy": "^1.12.0",
+    "loaders.css": "^0.1.2",
+    "node-sass": "^3.4.2",
+    "react-addons-css-transition-group": "^0.14.7",
+    "sass-loader": "^3.1.2",
+    "style-loader": "^0.13.0",
+    "webpack": "^1.3.0",
+     "webpack-dev-server": "^1.10.1"
+  }
+}
diff --git a/skyquake/plugins/project_management/routes.js b/skyquake/plugins/project_management/routes.js
new file mode 100644 (file)
index 0000000..8640a99
--- /dev/null
@@ -0,0 +1,12 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+var app = require('express').Router();
+var cors = require('cors');
+var utils = require('../../framework/core/api_utils/utils.js')
+ // Begin Accounts API
+
+    utils.passThroughConstructor(app);
+
+module.exports = app;
diff --git a/skyquake/plugins/project_management/scripts/build.sh b/skyquake/plugins/project_management/scripts/build.sh
new file mode 100755 (executable)
index 0000000..c4389c7
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+PLUGIN_NAME=project-management
+# change to the directory of this script
+THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+cd $THIS_DIR
+cd ..
+
+echo 'Building plugin '$PLUGIN_NAME
+echo 'Fetching third-party node_modules for '$PLUGIN_NAME
+npm install
+echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
+echo 'Packaging '$PLUGIN_NAME' using webpack'
+ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+echo 'Packaging '$PLUGIN_NAME' using webpack... done'
+echo 'Building plugin '$PLUGIN_NAME'... done'
diff --git a/skyquake/plugins/project_management/scripts/install.sh b/skyquake/plugins/project_management/scripts/install.sh
new file mode 100755 (executable)
index 0000000..af71fc5
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+plugin=project-management
+source_dir=$1
+dest_dir=$2
+bcache_dir=$3
+
+# Create destination and build cache directories
+mkdir -p $dest_dir
+mkdir -p $bcache_dir
+
+# Create necessary directories under dest and cache dirs
+mkdir -p $dest_dir/framework
+mkdir -p $dest_dir/plugins
+mkdir -p $bcache_dir/framework
+mkdir -p $bcache_dir/plugins
+
+# Copy over built plugin's public folder, config.json, routes.js and api folder as per installed_plugins.txt
+mkdir -p $dest_dir/plugins/$plugin
+cp -Lrf $source_dir/public $dest_dir/plugins/$plugin/.
+cp -Lrf $source_dir/config.json $dest_dir/plugins/$plugin/.
+cp -Lrf $source_dir/routes.js $dest_dir/plugins/$plugin/.
+cp -Lrp $source_dir/api $dest_dir/plugins/$plugin/.
+tar -cf $dest_dir/plugins/$plugin/node_modules.tar node_modules package.json -C $source_dir
+#cp -Lrp $source_dir/node_modules $dest_dir/plugins/$plugin/.
+mkdir -p $bcache_dir/plugins/$plugin
+cp -Lrf $source_dir/public $bcache_dir/plugins/$plugin/.
+cp -Lrf $source_dir/config.json $bcache_dir/plugins/$plugin/.
+cp -Lrf $source_dir/routes.js $bcache_dir/plugins/$plugin/.
+cp -Lrp $source_dir/api $bcache_dir/plugins/$plugin/.
+tar -cf $bcache_dir/plugins/$plugin/node_modules.tar $source_dir/node_modules $source_dir/package.json
+#cp -Lrp $source_dir/node_modules $bcache_dir/plugins/$plugin/.
diff --git a/skyquake/plugins/project_management/server.js b/skyquake/plugins/project_management/server.js
new file mode 100644 (file)
index 0000000..eb140b7
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+var express = require('express');
+var path = require('path');
+var httpProxy = require('http-proxy');
+var bodyParser = require('body-parser');
+var cors = require('cors');
+var session = require('express-session');
+var proxy = httpProxy.createProxyServer();
+var app = express();
+
+var isProduction = process.env.NODE_ENV === 'production';
+var port = isProduction ? 8080 : 8888;
+var publicPath = path.resolve(__dirname, 'public');
+
+if (!isProduction) {
+
+  //Routes for local development
+  var lpRoutes = require('./routes.js');
+
+  app.use(express.static(publicPath));
+  app.use(session({
+    secret: 'ritio rocks',
+  }));
+  app.use(bodyParser.urlencoded({
+      extended: true
+  }));
+  app.use(bodyParser.json());
+  app.use(cors());
+  app.use('/', lpRoutes);
+  var bundle = require('./server/bundle.js');
+  bundle();
+
+  app.all('/build/*', function (req, res) {
+    proxy.web(req, res, {
+        target: 'http://localhost:8080'
+    });
+  });
+
+}
+proxy.on('error', function(e) {
+  console.log('Could not connect to proxy, please try again...');
+});
+
+app.listen(port, function () {
+  console.log('Server running on port ' + port);
+});
+
+app.get('/*')
diff --git a/skyquake/plugins/project_management/src/dashboard/dashboard.jsx b/skyquake/plugins/project_management/src/dashboard/dashboard.jsx
new file mode 100644 (file)
index 0000000..bc67dbf
--- /dev/null
@@ -0,0 +1,428 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import ProjectManagementStore from './projectMgmtStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import 'style/layout.scss';
+import './projectMgmt.scss';
+import {Panel, PanelWrapper} from 'widgets/panel/panel';
+import {InputCollection, FormSection} from 'widgets/form_controls/formControls.jsx';
+
+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'
+
+import ROLES from 'utils/roleConstants.js';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
+class ProjectManagementDashboard extends React.Component {
+    constructor(props) {
+        super(props);
+        this.Store = this.props.flux.stores.hasOwnProperty('ProjectManagementStore') ? this.props.flux.stores.ProjectManagementStore : this.props.flux.createStore(ProjectManagementStore);
+        this.Store.getProjects();
+        this.Store.getUsers();
+        this.state = this.Store.getState();
+        this.actions = this.state.actions;
+    }
+    componentDidUpdate() {
+        let self = this;
+        ReactDOM.findDOMNode(this.projectList).addEventListener('transitionend', this.onTransitionEnd, false);
+        setTimeout(function() {
+            let element = self[`project-ref-${self.state.activeIndex}`]
+            element && !isElementInView(element) && element.scrollIntoView({block: 'end', behavior: 'smooth'});
+        })
+    }
+    componentWillMount() {
+        this.Store.listen(this.updateState);
+    }
+    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)
+    }
+    addProject = () => {
+        this.actions.handleAddProject();
+    }
+    viewProject = (un, index) => {
+        this.actions.viewProject(un, index);
+    }
+    editProject = () => {
+        this.actions.editProject(false);
+    }
+    cancelEditProject = () => {
+        this.actions.editProject(true)
+    }
+    closePanel = () => {
+        this.actions.handleCloseProjectPanel();
+    }
+
+    deleteProject = (e) => {
+        e.preventDefault();
+        e.stopPropagation();
+        if (confirm('Are you sure you want to delete this project?')) {
+            this.Store.deleteProject({
+                'name': this.state['name']
+            });
+        }
+    }
+    createProject = (e) => {
+        let self = this;
+        e.preventDefault();
+        e.stopPropagation();
+        let projectName = self.state['name'];
+        let projectUsers = self.state.projectUsers;
+        let cleanUsers = this.cleanUsers(projectUsers, projectName);
+
+
+        this.Store.createProject({
+            'name': projectName,
+            'description': self.state.description,
+            'project-config' : {
+                'user': cleanUsers
+            }
+        });
+    }
+    updateProject = (e) => {
+        let self = this;
+        e.preventDefault();
+        e.stopPropagation();
+         let projectName = self.state['name'];
+        let projectUsers = self.state.projectUsers;
+        let cleanUsers = this.cleanUsers(projectUsers, projectName);
+
+
+        this.Store.updateProject(_.merge({
+            'name': projectName,
+            'description': self.state.description,
+            'project-config' : {
+                'user': cleanUsers
+            }
+        }));
+    }
+    cleanUsers(projectUsers, projectName) {
+        let cleanUsers = [];
+        //Remove null values from role
+        projectUsers.map((u) => {
+            let cleanRoles = [];
+           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 ) {
+            } else {
+                delete r.keys;
+                    // r.keys = projectName;
+                    cleanRoles.push(r)
+            }
+           });
+           u.role = cleanRoles;
+           cleanUsers.push(u);
+        });
+        return cleanUsers;
+    }
+     evaluateSubmit = (e) => {
+        if (e.keyCode == 13) {
+            if (this.props.isEdit) {
+                this.updateProject(e);
+            } else {
+                this.createProject(e);
+            }
+            e.preventDefault();
+            e.stopPropagation();
+        }
+    }
+    updateSelectedUser = (e) => {
+        this.setState({
+            selected
+        })
+    }
+    addUserToProject = (e) => {
+        let selectUserList = this.selectUserList;
+        console.log(ReactDOM.findDOMNode(selectUserList))
+        this.actions.handleAddUser(e);
+    }
+    removeUserFromProject = (userIndex, e) => {
+        this.actions.handleRemoveUserFromProject(userIndex);
+    }
+    updateUserRoleInProject = (userIndex, roleIndex, e) => {
+        this.actions.handleUpdateUserRoleInProject({
+            userIndex,
+            roleIndex,
+            value: JSON.parse(e.target.value)
+        })
+    }
+    toggleUserRoleInProject = (userIndex, roleIndex, e) => {
+        this.actions.handleToggleUserRoleInProject({
+            userIndex,
+            roleIndex,
+            checked: JSON.parse(e.currentTarget.checked)
+        })
+    }
+    removeRoleFromUserInProject = (userIndex, roleIndex, e) => {
+        this.actions.handleRemoveRoleFromUserInProject({
+            userIndex,
+            roleIndex
+        })
+    }
+    addRoleToUserInProject = (userIndex, e) => {
+        this.actions.addRoleToUserInProject(userIndex);
+    }
+    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.editProject} />
+                </ButtonGroup>
+        );
+        let projectUsers = [];
+        self.state.projectUsers.map((u) => {
+            projectUsers.push(u['user-name']);
+        });
+        let availableUsers = state.users && state.users.filter((u) => {
+            return projectUsers.indexOf(u['user-name']) == -1
+        }).map((u) => {
+            return {
+                label: u['user-name'],
+                value: u
+            }
+        });
+
+        if(!this.state.isReadOnly) {
+            formButtonsHTML = (
+                                state.isEdit ?
+                                (
+                                    <ButtonGroup className="buttonGroup">
+                                        <Button label="Update" type="submit" onClick={this.updateProject} />
+                                        <Button label="Delete" onClick={this.deleteProject} />
+                                        <Button label="Cancel" onClick={this.cancelEditProject} />
+                                    </ButtonGroup>
+                                )
+                                : (
+                                    <ButtonGroup className="buttonGroup">
+                                        <Button label="Create" type="submit" onClick={this.createProject}  />
+                                    </ButtonGroup>
+                                )
+                            )
+        }
+
+        html = (
+            <PanelWrapper className={`row projectManagement ${!this.state.projectOpen ? 'projectList-open' : ''}`} style={{'alignContent': 'center', 'flexDirection': 'row'}} >
+                <PanelWrapper ref={(div) => { this.projectList = div}} className={`column projectList expanded ${this.state.projectOpen ? 'collapsed ' : ' '} ${this.state.hideColumns ? 'hideColumns ' : ' '}`}>
+                    <Panel title="Project List" style={{marginBottom: 0}} no-corners>
+                        <div className="tableRow tableRow--header">
+                            <div className="projectName">
+                                Project Name
+                            </div>
+                            <div>
+                                Description
+                            </div>
+                        </div>
+                        {state.projects && state.projects.map((u, k) => {
+                            let platformRoles = [];
+                            for(let role in u.platformRoles) {
+                                platformRoles.push(<div>{`${role}: ${u.platformRoles[role]}`}</div>)
+                            }
+                            return (
+                                <div onClick={self.viewProject.bind(null, u, k)} ref={(el) => this[`project-ref-${k}`] = el} className={`tableRow tableRow--data ${((self.state.activeIndex == k) && self.state.projectOpen) ? 'tableRow--data-active' : ''}`} key={k}>
+                                    <div
+                                        className={`projectName projectName-header ${((self.state.activeIndex == k) && self.state.projectOpen) ? 'activeProject' : ''}`}
+                                        >
+                                        {u['name']}
+                                    </div>
+                                    <div>
+                                        {u['description']}
+                                    </div>
+
+
+                                </div>
+                            )
+                        })}
+                    </Panel>
+                    <SkyquakeRBAC className="rbacButtonGroup">
+                        <ButtonGroup  className="buttonGroup">
+                            <Button label="Add Project" onClick={this.addProject} />
+                        </ButtonGroup>
+                    </SkyquakeRBAC>
+                </PanelWrapper>
+                <PanelWrapper onKeyUp={this.evaluateSubmit}
+                    className={`ProjectAdmin column`}>
+                    <Panel
+                        title={state.isEdit ? state['name'] : 'Create Project'}
+                        style={{marginBottom: 0}}
+                        hasCloseButton={this.closePanel}
+                        no-corners>
+                        <FormSection title="PROJECT INFO">
+                        {
+                            this.state.isEdit ?
+                                null
+                                : <Input  readonly={state.isReadOnly}  label="Name" value={state['name']} onChange={this.updateInput.bind(null, 'name')} />
+                        }
+                            <Input readonly={state.isReadOnly} type="textarea" label="Description" value={state['description']}  onChange={this.updateInput.bind(null, 'description')}></Input>
+                        </FormSection>
+                        <FormSection title="USER ROLES"  className="userTable">
+
+                        <table>
+                            <thead>
+                                <tr>
+                                    {!state.isReadOnly ? <td></td> : null}
+                                    <td>User Name</td>
+                                    {
+                                        state.roles.map((r,i) => {
+                                            return <td key={i}>{r}</td>
+                                        })
+                                    }
+                                </tr>
+                            </thead>
+                            <tbody>
+                                {
+                            state.projectUsers.map((u,i)=> {
+                                let userRoles = u.role && u.role.map((r) => {
+                                    return r.role;
+                                }) || [];
+                                return (
+                                    <tr key={i}>
+                                        {!state.isReadOnly ? <td><span
+                                                                    className="removeInput"
+                                                                    onClick={self.removeUserFromProject.bind(self, i)}
+                                                                >
+                                                                    <img src={imgRemove} />
+
+                                                                </span></td> : null}
+                                        <td>
+                                            {u['user-name']}
+                                        </td>
+                                        {
+                                            state.roles.map((r,j) => {
+                                                return <td key={j}><Input readonly={state.isReadOnly} type="checkbox" onChange={self.toggleUserRoleInProject.bind(self, i, j)} checked={(userRoles.indexOf(r) > -1)} /></td>
+                                            })
+                                        }
+                                    </tr>
+                                )
+                            })
+                        }
+                            </tbody>
+                        </table>
+
+                            {
+                                !state.isReadOnly ?
+                                    <div className="tableRow tableRow--header">
+                                        <div>
+                                            <div className="addUser">
+                                                <SelectOption
+                                                    onChange={this.actions.handleSelectedUser}
+                                                    value={state.selectedUser}
+                                                    initial={true}
+                                                    options={availableUsers}
+                                                    ref={(el) => self.selectUserList = el}
+                                                />
+                                                <span className="addInput" onClick={this.addUserToProject}><img src={imgAdd} />
+                                                    Add User
+                                                </span>
+                                            </div>
+                                        </div>
+                                    </div> : null
+                            }
+
+                        </FormSection>
+
+                    </Panel>
+                     <SkyquakeRBAC allow={[PROJECT_ROLES.PROJECT_ADMIN]} project={this.state.name} className="rbacButtonGroup">
+                        {formButtonsHTML}
+                     </SkyquakeRBAC>
+                </PanelWrapper>
+
+
+            </PanelWrapper>
+        );
+        return html;
+    }
+}
+// onClick={this.Store.update.bind(null, Account)}
+ProjectManagementDashboard.contextTypes = {
+    router: React.PropTypes.object
+};
+
+ProjectManagementDashboard.defaultProps = {
+    projectList: [],
+    selectedProject: {}
+}
+
+export default SkyquakeComponent(ProjectManagementDashboard);
+
+
+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/>)
+    }
+}
+
+
+
+
diff --git a/skyquake/plugins/project_management/src/dashboard/projectMgmt.scss b/skyquake/plugins/project_management/src/dashboard/projectMgmt.scss
new file mode 100644 (file)
index 0000000..eca6a59
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+/* If there's time this really needs to be rewritten. Ideally with smooth animations.*/
+@import "style/_colors.scss";
+
+.projectManagement {
+        max-width: 1200px;
+
+    .skyquakePanel-wrapper {
+        overflow-x: hidden;
+    }
+    .projectList {
+
+        -ms-flex: 0 1 200px;
+        flex: 0 1 200px;
+
+        .activeUser {
+            font-weight:bold;
+        }
+
+        /* transition: all 2s;*/
+        &.expanded {
+            -ms-flex: 1 1 100%;
+            flex: 1 1 100%;
+            /* transition: all 300ms;*/
+            .tableRow>div:not(.projectName) {
+                opacity: 1;
+                /* width:auto;*/
+                /* transition: width 600ms;*/
+                /* transition: opacity 300ms;*/
+            }
+            &.collapsed {
+                -ms-flex: 0 1 200px;
+                flex: 0 1 200px;
+                /* transition: all 2s;*/
+                .tableRow>div:not(.projectName) {
+                    /* opacity: 0;*/
+                    /* width:0px;*/
+                    display:none;
+                    overflow:hidden;
+                    /* transition: all 600ms;*/
+                }
+            }
+        }
+        &.hideColumns {
+            overflow:hidden;
+            >div {
+                overflow:hidden;
+            }
+            .tableRow>div:not(.projectName) {
+                width: 0px;
+                /* transition: all 600ms;*/
+            }
+            .projectName {
+                &--header {
+                    /* display:none;*/
+             }
+            }
+        }
+        .projectName {
+            cursor:pointer;
+        }
+
+
+    }
+
+    .projectAdmin {
+            -ms-flex: 1 1;
+            flex: 1 1;
+            width:auto;
+            opacity:1;
+
+        textarea{
+            height: 100px;
+        }
+    }
+    &.projectList-open {
+        .projectAdmin {
+            -ms-flex: 0 1 0px;
+                flex: 0 1 0px;
+            opacity:0;
+            /* width: 0px;*/
+            display:none;
+            /* transition: opacity 300ms;*/
+            /* transition: width 600ms;*/
+
+        }
+    }
+    .rbacButtonGroup {
+        margin: 0 0.5rem 0.5rem;
+        background: #ddd;
+        padding-bottom: 0.5rem;
+        padding: 0.5rem 0;
+    }
+    .buttonGroup {
+        border-top: #d3d3d3 1px solid;
+        padding-top:0.5rem;
+    }
+    .addUser {
+        display:-ms-flexbox;
+        display:flex;
+        -ms-flex-direction:row;
+            flex-direction:row;
+        label {
+            -ms-flex: 0 1;
+                flex: 0 1;
+                width:150px;
+            select {
+                width:150px;
+            }
+        }
+    }
+    .projectUsers {
+        .userName {
+            -ms-flex-pack: start;
+            justify-content: flex-start;
+            padding-top: 0.75rem;
+        }
+        select {
+            margin-bottom:0.5rem;
+        }
+        .addRole {
+            margin:.25rem 0;
+        }
+        .buttonGroup {
+            display:-ms-flexbox;
+            display:flex;
+        }
+
+    }
+    .projectUsers.tableRow--data:hover {
+            background:none;
+            color: black;
+        }
+
+    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;
+            }
+        }
+    }
+    .userTable {
+        .FormSection-body {
+            max-width: 952px;
+            overflow-x: auto;
+        }
+    }
+}
+
+
+
+.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 {
+
+    }
+}
+.tableRow {
+    display:-ms-flexbox;
+    display:flex;
+    -ms-flex-wrap: nowrap;
+    flex-wrap: nowrap;
+    padding: 0.25rem;
+    margin: .125rem 0;
+    >div {
+        padding: 0.25rem;
+        -ms-flex: 1 1 33%;
+            flex: 1 1 33%;
+        display: -ms-flexbox;
+        display: flex;
+        -ms-flex-direction: column;
+            flex-direction: column;
+        -ms-flex-pack: center;
+            justify-content: center;
+    }
+    &--header {
+        font-weight:bold;
+    }
+    &--data {
+        &:hover:not(&-active) {
+            background:$neutral-dark-1;
+        }
+        &:hover, .activeUser, &-active{
+            cursor:pointer;
+            color:white;
+        }
+        .activeUser, &-active{
+            background: #00acee;
+        }
+    }
+}
+
+.addInput, .removeInput {
+    display:-ms-flexbox;
+    display:flex;
+    -ms-flex-align:center;
+    align-items:center;
+    margin-left: 1rem;
+
+    font-size:0.75rem;
+    text-transform:uppercase;
+    font-weight:bold;
+
+    cursor:pointer;
+    img {
+        height:0.75rem;
+        margin-right:0.5rem;
+        width:auto;
+    }
+    span {
+        color: #5b5b5b;
+        text-transform: uppercase;
+    }
+}
diff --git a/skyquake/plugins/project_management/src/dashboard/projectMgmtActions.js b/skyquake/plugins/project_management/src/dashboard/projectMgmtActions.js
new file mode 100644 (file)
index 0000000..5f2b1ea
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+module.exports = function(Alt) {
+   return Alt.generateActions(
+                                       'handleUpdateInput',
+                                       'handleAddProjectItem',
+                                       'handleRemoveProjectItem',
+                                       'handleUpdateProjectRole',
+                                       'viewProject',
+                                       'editProject',
+                                       'handleCloseProjectPanel',
+                                       'handleHideColumns',
+                                       'handleSelectedUser',
+                                       'handleSelectedRole',
+                                       'handleAddUser',
+                                       'handleRemoveUserFromProject',
+                                       'getProjectsSuccess',
+                                       'getUsersSuccess',
+                                       'getProjectsNotification',
+                                       'handleDisabledChange',
+                                       'handlePlatformRoleUpdate',
+                                       'handleAddProject',
+                                       'handleCreateProject',
+                                       'handleUpdateProject',
+                                       'handleUpdateSelectedUser',
+                                       'handleUpdateUserRoleInProject',
+                                       'handleToggleUserRoleInProject',
+                                       'addRoleToUserInProject',
+                                       'handleRemoveRoleFromUserInProject',
+                                       'updateProjectSuccess',
+                                       'createProjectSuccess',
+                                       'deleteProjectSuccess'
+                                       );
+}
diff --git a/skyquake/plugins/project_management/src/dashboard/projectMgmtSource.js b/skyquake/plugins/project_management/src/dashboard/projectMgmtSource.js
new file mode 100644 (file)
index 0000000..55646e8
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * 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
+        },
+        getProjects: {
+          remote: function() {
+              return new Promise(function(resolve, reject) {
+                $.ajax({
+                  url: `/project?api_server=${API_SERVER}`,
+                  type: 'GET',
+                  beforeSend: Utils.addAuthorizationStub,
+                  success: function(data, textStatus, jqXHR) {
+                    resolve(data.project);
+                  }
+                }).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.getProjectsSuccess,
+                    loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        updateProject: {
+          remote: function(state, project) {
+            return new Promise(function(resolve, reject) {
+              $.ajax({
+                  url: `/project?api_server=${API_SERVER}`,
+                  type: 'PUT',
+                  data: project,
+                  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 project.'
+          }),
+          success: Alt.actions.global.updateProjectSuccess,
+          loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        deleteProject: {
+          remote: function(state, project) {
+            return new Promise(function(resolve, reject) {
+              $.ajax({
+                url: `/project/${project['name']}?api_server=${API_SERVER}`,
+                type: 'DELETE',
+                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.deleteProjectSuccess,
+          loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        createProject: {
+            remote: function(state, project) {
+
+              return new Promise(function(resolve, reject) {
+                $.ajax({
+                  url: `/project?api_server=${API_SERVER}`,
+                  type: 'POST',
+                  data: project,
+                  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.createProjectSuccess,
+            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/project_management/src/dashboard/projectMgmtStore.js b/skyquake/plugins/project_management/src/dashboard/projectMgmtStore.js
new file mode 100644 (file)
index 0000000..71c10a2
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import ProjectManagementActions from './projectMgmtActions.js';
+import ProjectManagementSource from './projectMgmtSource.js';
+import ROLES from 'utils/roleConstants.js';
+import _ from 'lodash';
+export default class ProjectManagementStore {
+    constructor() {
+        this.actions = ProjectManagementActions(this.alt);
+        this.bindActions(this.actions);
+        this.registerAsync(ProjectManagementSource);
+        this.projects = [];
+        this['name'] = '';
+        this['description'] = 'Some Description';
+        this.projectUsers = [];
+        this.selectedUser = null;
+        this.selectedRole = null;
+        this.roles = Object.keys(ROLES.PROJECT).map((p) => {
+            return ROLES.PROJECT[p];
+        })
+        // this.roles = ['rw-project:project-admin', 'rw-project:project-oper', 'rw-project:project-create'];
+        this.users = [];
+        this.activeIndex = null;
+        this.isReadOnly = true;
+        this.projectOpen = 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
+        });
+    }
+    viewProject(data) {
+        let project = data[0];
+        let projectIndex = data[1];
+
+        let ProjectData = {
+            'name': project['name'],
+            'description': project['description'],
+            'projectUsers': project['project-config'] && project['project-config']['user'] || []
+        }
+        let state = _.merge({
+            activeIndex: projectIndex,
+            projectOpen: true,
+            isEdit: true,
+            isReadOnly: true
+        }, ProjectData);
+        this.setState(state)
+    }
+    editProject(isEdit) {
+        this.setState({
+            isReadOnly: isEdit
+        })
+    }
+    handleCloseProjectPanel() {
+        this.setState({
+            projectOpen: false,
+            isEdit: false,
+            isReadOnly: true
+        })
+    }
+    handleHideColumns(e) {
+        if(this.projectOpen && 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
+        })
+    }
+    handleSelectedUser(event) {
+        this.setState({
+            selectedUser: JSON.parse(event.currentTarget.value)
+        })
+    }
+
+    handleSelectedRole(event) {
+        this.setState({
+            selectedRole: JSON.parse(event.currentTarget.value)
+        })
+    }
+    resetProject() {
+        let name = '';
+        let description = '';
+        return {
+            'name' : name,
+            'description' : description
+        }
+    }
+    handleAddProject() {
+        this.setState(_.merge( this.resetProject() ,
+              {
+                isEdit: false,
+                projectOpen: true,
+                activeIndex: null,
+                isReadOnly: false,
+                projectUsers: []
+            }
+        ))
+    }
+
+    handleUpdateSelectedUser(user) {
+        this.setState({
+            selectedUser: JSON.parse(user)
+        });
+    }
+    handleAddUser(e) {
+        let self = this;
+        let u = JSON.parse(this.selectedUser);
+        let r = this.selectedRole;
+        let projectUsers = this.projectUsers;
+        console.log('adding user')
+        projectUsers.push({
+          'user-name': u['user-name'],
+          'user-domain': u['user-domain'],
+          "role":[{
+                      "role": r,
+                      "keys": self.name
+            }
+          ]
+        })
+        this.setState({projectUsers, selectedUser: JSON.stringify(null)})
+    }
+    handleToggleUserRoleInProject(data) {
+        let self = this;
+        let {userIndex, roleIndex, checked} = data;
+        let projectUsers = this.projectUsers;
+        let selectedRole = self.roles[roleIndex];
+        if(checked) {
+            if(!projectUsers[userIndex].role) projectUsers[userIndex].role = [];
+            projectUsers[userIndex].role.push({
+                role: self.roles[roleIndex]
+            })
+        } else {
+            let role = projectUsers[userIndex].role;
+            let roleIndex = _.findIndex(role, {role:selectedRole})
+            projectUsers[userIndex].role.splice(roleIndex, 1)
+        }
+       self.setState({projectUsers});
+
+    }
+    handleUpdateUserRoleInProject(data) {
+        let {userIndex, roleIndex, value} = data;
+        let projectUsers = this.projectUsers;
+        projectUsers[userIndex].role[roleIndex].role = value;
+
+    }
+    addRoleToUserInProject(userIndex) {
+        let projectUsers = this.projectUsers;
+        if(!projectUsers[userIndex].role) {
+            projectUsers[userIndex].role = [];
+        }
+        projectUsers[userIndex].role.push({
+              'role': null
+            });
+        this.setState({
+            projectUsers
+        })
+    }
+    handleRemoveRoleFromUserInProject (data) {
+        let {userIndex, roleIndex} = data;
+        let projectUsers = this.projectUsers;
+        projectUsers[userIndex].role.splice(roleIndex, 1);
+        this.setState({
+            projectUsers
+        })
+    }
+    handleRemoveUserFromProject (userIndex) {
+        let projectUsers = this.projectUsers;
+        projectUsers.splice(userIndex, 1);
+        this.setState({
+            projectUsers
+        })
+    }
+    getProjectsSuccess(projects) {
+        this.alt.actions.global.hideScreenLoader.defer();
+        this.setState({projects: projects});
+    }
+    getUsersSuccess(users) {
+        console.log(users)
+        this.alt.actions.global.hideScreenLoader.defer();
+        this.setState({users});
+    }
+    updateProjectSuccess() {
+        this.alt.actions.global.hideScreenLoader.defer();
+        let self = this;
+        let projects = this.projects || [];
+        projects[this.activeIndex] = {
+            'name': this['name'],
+            'description': this['description'],
+            'project-config': {
+                'user': self.projectUsers
+            }
+        }
+        this.setState({
+            projects,
+            isEdit: true,
+            isReadOnly: true
+        })
+    }
+    deleteProjectSuccess() {
+        this.alt.actions.global.hideScreenLoader.defer();
+        let projects = this.projects;
+        projects.splice(this.activeIndex, 1);
+        this.setState({projects, projectOpen: false})
+    }
+    createProjectSuccess() {
+        this.alt.actions.global.hideScreenLoader.defer();
+        let projects = this.projects || [];
+        projects.push({
+            'name': this['name'],
+            'description': this['description']
+         });
+        let newState = {
+            projects,
+            isEdit: true,
+            isReadOnly: true,
+            activeIndex: projects.length - 1
+        };
+        _.merge(newState)
+        this.setState(newState);
+    }
+}
diff --git a/skyquake/plugins/project_management/src/main.js b/skyquake/plugins/project_management/src/main.js
new file mode 100644 (file)
index 0000000..5dc626f
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import "babel-polyfill";
+import { render } from 'react-dom';
+import SkyquakeRouter from 'widgets/skyquake_container/skyquakeRouter.jsx';
+const config = require('json!../config.json');
+
+let context = require.context('./', true, /^\.\/.*\.jsx$/);
+let router = SkyquakeRouter(config, context);
+let element = document.querySelector('#app');
+
+render(router, element);
+
+
diff --git a/skyquake/plugins/project_management/webpack.production.config.js b/skyquake/plugins/project_management/webpack.production.config.js
new file mode 100644 (file)
index 0000000..49ad631
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+var Webpack = require('webpack');
+var path = require('path');
+var nodeModulesPath = path.resolve(__dirname, 'node_modules');
+var buildPath = path.resolve(__dirname, 'public', 'build');
+var mainPath = path.resolve(__dirname, 'src', 'main.js');
+var uiPluginCmakeBuild = process.env.ui_plugin_cmake_build || false;
+var frameworkPath = uiPluginCmakeBuild?'../../../../skyquake/skyquake-build/framework':'../../framework';
+var HtmlWebpackPlugin = require('html-webpack-plugin');
+var CommonsPlugin = new require("webpack/lib/optimize/CommonsChunkPlugin")
+// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
+process.env.UV_THREADPOOL_SIZE=64;
+var config = {
+    devtool: 'source-map',
+    entry: mainPath,
+    output: {
+        path: buildPath,
+        filename: 'bundle.js',
+        publicPath: "build/"
+    },
+    resolve: {
+        extensions: ['', '.js', '.jsx', '.css', '.scss'],
+        root: path.resolve(frameworkPath),
+        alias: {
+            'widgets': path.resolve(frameworkPath) + '/widgets',
+            'style':  path.resolve(frameworkPath) + '/style',
+            'utils':  path.resolve(frameworkPath) + '/utils'
+        }
+    },
+    module: {
+        loaders: [{
+                test: /\.(jpe?g|png|gif|svg|ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/i,
+                loader: "file-loader"
+            },
+            {
+                test: /\.(js|jsx)$/,
+                exclude: /react-treeview/,
+                loader: 'babel-loader',
+                query: {
+                    presets: ["es2015", "stage-0", "react"]
+                }
+            }, {
+                test: /\.css$/,
+                loader: 'style!css'
+            }, {
+                test: /\.scss/,
+                loader: 'style!css!sass?includePaths[]='+ path.resolve(frameworkPath)
+            }
+        ]
+    },
+    plugins: [
+        new HtmlWebpackPlugin({
+            filename: '../index.html'
+            , templateContent: '<div id="app"></div>'
+        }),
+        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+    ]
+};
+module.exports = config;
diff --git a/skyquake/plugins/user_management/CMakeLists.txt b/skyquake/plugins/user_management/CMakeLists.txt
new file mode 100644 (file)
index 0000000..5235092
--- /dev/null
@@ -0,0 +1,38 @@
+# RIFT_IO_STANDARD_CMAKE_COPYRIGHT_HEADER(BEGIN)
+# Author(s): Kiran Kashalkar
+# Creation Date: 08/18/2015
+# RIFT_IO_STANDARD_CMAKE_COPYRIGHT_HEADER(END)
+
+##
+# DEPENDENCY ALERT
+# The submodule dependencies must be specified in the
+# .gitmodules.dep file at the top level (supermodule) directory
+# If this submodule depends other submodules remember to update
+# the .gitmodules.dep
+##
+
+cmake_minimum_required(VERSION 2.8)
+
+##
+# Submodule specific includes will go here,
+# These are specified here, since these variables are accessed
+# from multiple sub directories. If the variable is subdirectory
+# specific it must be declared in the subdirectory.
+##
+
+rift_externalproject_add(
+  user_management
+  DEPENDS skyquake
+  SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
+  CONFIGURE_COMMAND echo
+  BUILD_COMMAND
+    ${CMAKE_CURRENT_BINARY_DIR}/user_management/user_management-build/scripts/build.sh
+  INSTALL_COMMAND
+    ${CMAKE_CURRENT_SOURCE_DIR}/scripts/install.sh
+    ${CMAKE_CURRENT_BINARY_DIR}/user_management/user_management-build
+    ${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+    ${RIFT_SUBMODULE_INSTALL_PREFIX}/skyquake/${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+
+  BCACHE_COMMAND echo
+)
+
diff --git a/skyquake/plugins/user_management/config.json b/skyquake/plugins/user_management/config.json
new file mode 100644 (file)
index 0000000..38de12f
--- /dev/null
@@ -0,0 +1,30 @@
+{
+    "root": "public",
+    "name": "User Management",
+    "dashboard": "./dashboard/dashboard.jsx",
+    "order": 1,
+    "priority":1,
+    "admin_link": true,
+    "allow": ["rw-rbac-platform:super-admin", "rw-rbac-platform:platform-admin", "rw-rbac-platform:platform-oper"],
+    "routes": [
+    {
+        "label": "User Management Dashboard",
+        "route": "user-management",
+        "component": "./dashboard/dashboard.jsx",
+        "type": "internal",
+        "allow": ["rw-rbac-platform:super-admin", "rw-rbac-platform:platform-admin", "rw-rbac-platform:platform-oper"]
+    },{
+        "label": "Platform Role Management",
+        "route": "platform",
+        "component": "./platformRoleManagement/platformRoleManagement.jsx",
+        "type": "external"
+    },
+      {
+        "label": "User Profile",
+        "route": "user-profile",
+        "component": "./userProfile/userProfile.jsx",
+        "type": "internal",
+        "unique" : true
+    }
+    ]
+}
diff --git a/skyquake/plugins/user_management/package.json b/skyquake/plugins/user_management/package.json
new file mode 100644 (file)
index 0000000..4ad56af
--- /dev/null
@@ -0,0 +1,54 @@
+{
+  "name": "config",
+  "version": "1.0.0",
+  "description": "",
+  "main": "routes.js",
+  "scripts": {
+    "start": "rm -rf public/ && mkdir public && cd public && mkdir build && cp ../src/index.html ./ && node ../server.js"
+  },
+  "author": "RIFT.io",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "alt": "^0.18.3",
+    "bluebird": "^3.4.1",
+    "express": "^4.13.3",
+    "history": "^1.17.0",
+    "jquery": "^2.2.1",
+    "json-loader": "^0.5.4",
+    "lodash": "^4.10.0",
+    "normalizr": "^2.1.0",
+    "open-iconic": "^1.1.1",
+    "prismjs": "^1.4.1",
+    "react": "^0.14.8",
+    "react-breadcrumbs": "^1.3.9",
+    "react-crouton": "^0.2.7",
+    "react-dom": "^0.14.6",
+    "react-router": "^2.0.1",
+    "react-slick": "^0.11.1",
+    "react-tabs": "^0.5.3",
+    "react-treeview": "^0.4.2",
+    "request-promise": "^3.0.0",
+    "underscore": "^1.8.3"
+  },
+  "devDependencies": {
+    "babel-core": "^6.4.5",
+    "babel-loader": "^6.2.1",
+    "babel-polyfill": "^6.9.1",
+    "babel-preset-es2015": "^6.6.0",
+    "babel-preset-react": "^6.5.0",
+    "babel-preset-stage-0": "^6.3.13",
+    "babel-runtime": "^6.3.19",
+    "cors": "^2.7.1",
+    "css-loader": "^0.23.1",
+    "file-loader": "^0.8.5",
+    "html-webpack-plugin": "^2.9.0",
+    "http-proxy": "^1.12.0",
+    "loaders.css": "^0.1.2",
+    "node-sass": "^3.4.2",
+    "react-addons-css-transition-group": "^0.14.7",
+    "sass-loader": "^3.1.2",
+    "style-loader": "^0.13.0",
+    "webpack": "^1.3.0",
+     "webpack-dev-server": "^1.10.1"
+  }
+}
diff --git a/skyquake/plugins/user_management/routes.js b/skyquake/plugins/user_management/routes.js
new file mode 100644 (file)
index 0000000..8640a99
--- /dev/null
@@ -0,0 +1,12 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+var app = require('express').Router();
+var cors = require('cors');
+var utils = require('../../framework/core/api_utils/utils.js')
+ // Begin Accounts API
+
+    utils.passThroughConstructor(app);
+
+module.exports = app;
diff --git a/skyquake/plugins/user_management/scripts/build.sh b/skyquake/plugins/user_management/scripts/build.sh
new file mode 100755 (executable)
index 0000000..04c5221
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+PLUGIN_NAME=user-management
+# change to the directory of this script
+THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+cd $THIS_DIR
+cd ..
+
+echo 'Building plugin '$PLUGIN_NAME
+echo 'Fetching third-party node_modules for '$PLUGIN_NAME
+npm install
+echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
+echo 'Packaging '$PLUGIN_NAME' using webpack'
+ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+echo 'Packaging '$PLUGIN_NAME' using webpack... done'
+echo 'Building plugin '$PLUGIN_NAME'... done'
diff --git a/skyquake/plugins/user_management/scripts/install.sh b/skyquake/plugins/user_management/scripts/install.sh
new file mode 100755 (executable)
index 0000000..b883243
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+plugin=user-management
+source_dir=$1
+dest_dir=$2
+bcache_dir=$3
+
+# Create destination and build cache directories
+mkdir -p $dest_dir
+mkdir -p $bcache_dir
+
+# Create necessary directories under dest and cache dirs
+mkdir -p $dest_dir/framework
+mkdir -p $dest_dir/plugins
+mkdir -p $bcache_dir/framework
+mkdir -p $bcache_dir/plugins
+
+# Copy over built plugin's public folder, config.json, routes.js and api folder as per installed_plugins.txt
+mkdir -p $dest_dir/plugins/$plugin
+cp -Lrf $source_dir/public $dest_dir/plugins/$plugin/.
+cp -Lrf $source_dir/config.json $dest_dir/plugins/$plugin/.
+cp -Lrf $source_dir/routes.js $dest_dir/plugins/$plugin/.
+cp -Lrp $source_dir/api $dest_dir/plugins/$plugin/.
+tar -cf $dest_dir/plugins/$plugin/node_modules.tar node_modules package.json -C $source_dir
+#cp -Lrp $source_dir/node_modules $dest_dir/plugins/$plugin/.
+mkdir -p $bcache_dir/plugins/$plugin
+cp -Lrf $source_dir/public $bcache_dir/plugins/$plugin/.
+cp -Lrf $source_dir/config.json $bcache_dir/plugins/$plugin/.
+cp -Lrf $source_dir/routes.js $bcache_dir/plugins/$plugin/.
+cp -Lrp $source_dir/api $bcache_dir/plugins/$plugin/.
+tar -cf $bcache_dir/plugins/$plugin/node_modules.tar $source_dir/node_modules $source_dir/package.json
+#cp -Lrp $source_dir/node_modules $bcache_dir/plugins/$plugin/.
diff --git a/skyquake/plugins/user_management/server.js b/skyquake/plugins/user_management/server.js
new file mode 100644 (file)
index 0000000..eb140b7
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+var express = require('express');
+var path = require('path');
+var httpProxy = require('http-proxy');
+var bodyParser = require('body-parser');
+var cors = require('cors');
+var session = require('express-session');
+var proxy = httpProxy.createProxyServer();
+var app = express();
+
+var isProduction = process.env.NODE_ENV === 'production';
+var port = isProduction ? 8080 : 8888;
+var publicPath = path.resolve(__dirname, 'public');
+
+if (!isProduction) {
+
+  //Routes for local development
+  var lpRoutes = require('./routes.js');
+
+  app.use(express.static(publicPath));
+  app.use(session({
+    secret: 'ritio rocks',
+  }));
+  app.use(bodyParser.urlencoded({
+      extended: true
+  }));
+  app.use(bodyParser.json());
+  app.use(cors());
+  app.use('/', lpRoutes);
+  var bundle = require('./server/bundle.js');
+  bundle();
+
+  app.all('/build/*', function (req, res) {
+    proxy.web(req, res, {
+        target: 'http://localhost:8080'
+    });
+  });
+
+}
+proxy.on('error', function(e) {
+  console.log('Could not connect to proxy, please try again...');
+});
+
+app.listen(port, function () {
+  console.log('Server running on port ' + port);
+});
+
+app.get('/*')
diff --git a/skyquake/plugins/user_management/src/dashboard/dashboard.jsx b/skyquake/plugins/user_management/src/dashboard/dashboard.jsx
new file mode 100644 (file)
index 0000000..a3b6a63
--- /dev/null
@@ -0,0 +1,451 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import UserManagementStore from './userMgmtStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import 'style/layout.scss';
+import './userMgmt.scss';
+import {Panel, PanelWrapper} from 'widgets/panel/panel';
+import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+
+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';
+
+import ROLES from 'utils/roleConstants.js';
+const PLATFORM = ROLES.PLATFORM;
+
+class UserManagementDashboard extends React.Component {
+    constructor(props) {
+        super(props);
+        this.Store = this.props.flux.stores.hasOwnProperty('UserManagementStore') ? this.props.flux.stores.UserManagementStore : this.props.flux.createStore(UserManagementStore);
+       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();
+        if (confirm('Are you sure you want to delete this user?')) {
+            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.state['user-name'],
+                            '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>
+        );
+        if(!this.state.isReadOnly) {
+            passwordSectionHTML = ( this.state.isEdit ?
+                                        (
+                                            <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>
+                                        ) :
+                                        (
+                                            <FormSection title="CREATE PASSWORD">
+                                                <Input label="CREATE PASSWORD" type="password" value={state.newPassword}  onChange={this.updateInput.bind(null, 'new-password')}/>
+                                                <Input label="REPEAT PASSWORD" type="password"  value={state.repeatNewPassword}  onChange={this.updateInput.bind(null, 'confirm-password')}/>
+                                            </FormSection>
+                                        )
+                                    );
+            formButtonsHTML = (
+                                state.isEdit ?
+                                (
+                                    <ButtonGroup className="buttonGroup">
+                                        <Button label="Update" type="submit" onClick={this.updateUser} />
+                                        <Button label="Delete" onClick={this.deleteUser} />
+                                        <Button label="Cancel" onClick={this.cancelEditUser} />
+                                    </ButtonGroup>
+                                )
+                                : (
+                                    <ButtonGroup className="buttonGroup">
+                                        <Button label="Create" type="submit" onClick={this.createUser}  />
+                                    </ButtonGroup>
+                                )
+                            )
+        }
+        html = (
+            <PanelWrapper column>
+                <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} >
+                    <AppHeader nav={[{name: 'PLATFORM ROLE MANAGEMENT', onClick: this.context.router.push.bind(this, {pathname: '/platform'})}]}/>
+                </SkyquakeRBAC>
+                <PanelWrapper className={`row userManagement ${!this.state.userOpen ? 'userList-open' : ''}`} style={{'flexDirection': 'row'}} >
+                    <PanelWrapper ref={(div) => { this.UserList = div}} className={`column userList expanded ${this.state.userOpen ? 'collapsed ' : ' '} ${this.state.hideColumns ? 'hideColumns ' : ' '}`}>
+                        <Panel title="User List" style={{marginBottom: 0}} no-corners>
+                            <div className="tableRow tableRow--header">
+                                <div className="userName">
+                                    Username
+                                </div>
+                                <div>
+                                    Domain
+                                </div>
+                            </div>
+                            {state.users && state.users.map((u, k) => {
+                                let platformRoles = [];
+                                for(let role in u.platformRoles) {
+                                    platformRoles.push(<div>{`${role}: ${u.platformRoles[role]}`}</div>)
+                                }
+                                return (
+                                    <div ref={(el) => this[`user-ref-${k}`] = el} className={`tableRow tableRow--data ${((self.state.activeIndex == k) && self.state.userOpen) ? 'tableRow--data-active' : ''}`}
+                                        key={k}
+                                        onClick={self.viewUser.bind(null, u, k)}>
+                                        <div
+                                            className={`userName userName-header ${((self.state.activeIndex == k) && self.state.userOpen) ? 'activeUser' : ''}`}
+                                            >
+                                            {u['user-name']}
+                                        </div>
+                                        <div>
+                                            {u['user-domain']}
+                                        </div>
+
+
+                                    </div>
+                                )
+                            })}
+                        </Panel>
+                        <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} className="rbacButtonGroup">
+                            <ButtonGroup  className="buttonGroup">
+                                <Button label="Add User" onClick={this.addUser} />
+                        </ButtonGroup>
+                        </SkyquakeRBAC>
+                    </PanelWrapper>
+                    <PanelWrapper onKeyUp={this.evaluateSubmit}
+                        className={`userAdmin column`}>
+                        <Panel
+                            title={state.isEdit ? state['user-name'] : 'Create User'}
+                            style={{marginBottom: 0}}
+                            hasCloseButton={this.closePanel}
+                            no-corners>
+                            <FormSection title="USER INFO">
+                                {
+                                    this.state.isEdit ?
+                                        null
+                                        : <Input  readonly={state.isReadOnly}  label="Username" value={state['user-name']} onChange={this.updateInput.bind(null, 'user-name')} />
+                                }
+                                <Input readonly={true} label="Domain" value={state['user-domain']}  onChange={this.updateInput.bind(null, 'user-domain')}></Input>
+                            </FormSection>
+                            <FormSection title="PLATFORM ROLES" style={{display:'none'}}>
+                                <Input label="Super Admin" onChange={this.platformChange.bind(null, 'super_admin')} checked={state.platformRoles.super_admin} type="checkbox" />
+                                <Input label="Platform Admin" onChange={this.platformChange.bind(null, 'platform_admin')}  checked={state.platformRoles.platform_admin} type="checkbox" />
+                                <Input label="Platform Oper" onChange={this.platformChange.bind(null, 'platform_oper')}  checked={state.platformRoles.platform_oper} type="checkbox" />
+                            </FormSection>
+                            <FormSection title="PROJECT ROLES" style={{display:'none'}}>
+                                <InputCollection
+                                    inital={true}
+                                    type='select'
+                                    readonly={state.isReadOnly}
+                                    options={state.projectRolesOptions}
+                                    collection={state.projectRoles}
+                                    onChange={this.updateProjectRole}
+                                    AddItemFn={this.addProjectRole}
+                                    RemoveItemFn={this.removeProjectRole}
+                                    />
+                            </FormSection>
+                            {passwordSectionHTML}
+
+                        </Panel>
+                        <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} className="rbacButtonGroup">
+                            {formButtonsHTML}
+                        </SkyquakeRBAC>
+                    </PanelWrapper>
+                </PanelWrapper>
+            </PanelWrapper>
+        );
+        return html;
+    }
+}
+// onClick={this.Store.update.bind(null, Account)}
+UserManagementDashboard.contextTypes = {
+    router: React.PropTypes.object,
+    userProfile: React.PropTypes.object
+};
+
+UserManagementDashboard.defaultProps = {
+    userList: [],
+    selectedUser: {}
+}
+
+export default SkyquakeComponent(UserManagementDashboard);
+
+
+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/dashboard/userMgmt.scss b/skyquake/plugins/user_management/src/dashboard/userMgmt.scss
new file mode 100644 (file)
index 0000000..a265f0d
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+/* If there's time this really needs to be rewritten. Ideally with smooth animations.*/
+@import "style/_colors.scss";
+
+.userManagement {
+        max-width: 900px;
+
+    .skyquakePanel-wrapper {
+        overflow-x: hidden;
+    }
+    .userList {
+
+        -ms-flex: 0 1 200px;
+        flex: 0 1 200px;
+
+        .activeUser {
+            font-weight:bold;
+        }
+
+        /* transition: all 2s;*/
+        &.expanded {
+            -ms-flex: 1 1 100%;
+            flex: 1 1 100%;
+            /* transition: all 300ms;*/
+            .tableRow>div:not(.userName) {
+                opacity: 1;
+                /* width:auto;*/
+                /* transition: width 600ms;*/
+                /* transition: opacity 300ms;*/
+            }
+            &.collapsed {
+                -ms-flex: 0 1 200px;
+                flex: 0 1 200px;
+                /* transition: all 2s;*/
+                .tableRow>div:not(.userName) {
+                    /* opacity: 0;*/
+                    /* width:0px;*/
+                    display:none;
+                    overflow:hidden;
+                    /* transition: all 600ms;*/
+                }
+            }
+        }
+        &.hideColumns {
+            overflow:hidden;
+            >div {
+                overflow:hidden;
+            }
+            .tableRow>div:not(.userName) {
+                width: 0px;
+                /* transition: all 600ms;*/
+            }
+            .userName {
+                &--header {
+                    /* display:none;*/
+             }
+            }
+        }
+        .userName {
+            cursor:pointer;
+        }
+
+    }
+
+    .userAdmin {
+            -ms-flex: 1 1;
+            flex: 1 1;
+            width:auto;
+            opacity:1;
+    }
+    &.userList-open {
+        .userAdmin {
+            -ms-flex: 0 1 0px;
+                flex: 0 1 0px;
+            opacity:0;
+            /* width: 0px;*/
+            display:none;
+            /* transition: opacity 300ms;*/
+            /* transition: width 600ms;*/
+
+        }
+    }
+    .rbacButtonGroup, .buttonSection {
+        margin: 0 0.5rem 0.5rem;
+        background: #ddd;
+        padding-bottom: 0.5rem;
+        padding: 0.5rem 0;
+    }
+    .buttonGroup {
+        border-top: #d3d3d3 1px solid;
+        padding-top:0.5rem;
+    }
+    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;
+            }
+        }
+    }
+}
+
+
+
+
+.tableRow {
+    display:-ms-flexbox;
+    display:flex;
+    -ms-flex-wrap: nowrap;
+    flex-wrap: nowrap;
+    padding: 0.25rem;
+    margin: .125rem 0;
+    >div {
+        padding:0.25rem 1rem 0.25rem 0;
+        -ms-flex: 1 1 33%;
+            flex: 1 1 33%;
+    }
+    &--header {
+        font-weight:bold;
+    }
+    &--data {
+        &:hover:not(&-active) {
+            background:$neutral-dark-1;
+        }
+        &:hover, .activeUser, &-active{
+            cursor:pointer;
+            color:white;
+        }
+        .activeUser, &-active{
+            background: #00acee;
+        }
+    }
+
+
+    .userProfile {
+        &-table {
+            thead{
+                font-weight:bold;
+            }
+            font-size: 1rem;
+            tr {
+                td {
+                    vertical-align:top;
+                }
+            }
+        }
+    }
+}
+
+.addInput, .removeInput {
+    display:-ms-flexbox;
+    display:flex;
+    -ms-flex-align:center;
+    align-items:center;
+    margin-left: 1rem;
+
+    font-size:0.75rem;
+    text-transform:uppercase;
+    font-weight:bold;
+
+    cursor:pointer;
+    img {
+        height:0.75rem;
+        margin-right:0.5rem;
+        width:auto;
+    }
+    span {
+        color: #5b5b5b;
+        text-transform: uppercase;
+    }
+}
+
diff --git a/skyquake/plugins/user_management/src/dashboard/userMgmtActions.js b/skyquake/plugins/user_management/src/dashboard/userMgmtActions.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/dashboard/userMgmtSource.js b/skyquake/plugins/user_management/src/dashboard/userMgmtSource.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/dashboard/userMgmtStore.js b/skyquake/plugins/user_management/src/dashboard/userMgmtStore.js
new file mode 100644 (file)
index 0000000..19952fb
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import UserManagementActions from './userMgmtActions.js';
+import UserManagementSource from './userMgmtSource.js';
+import _ from 'lodash';
+export default class UserManagementStore {
+    constructor() {
+        this.actions = UserManagementActions(this.alt);
+        this.bindActions(this.actions);
+        this.registerAsync(UserManagementSource);
+        this.users = [];
+        this['user-name'] = '';
+        this['user-domain'] = 'system';
+        this.disabled = false;
+        this.platformRoles = {
+            super_admin: false,
+            platform_admin: false,
+            platform_oper: false
+        };
+        this.projectRoles = ['Project:Role'];
+        this.projectRolesOptions = ['Choose your adventure', 'Project:Role', 'Project:Another Role'];
+        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);
+    }
+}
diff --git a/skyquake/plugins/user_management/src/main.js b/skyquake/plugins/user_management/src/main.js
new file mode 100644 (file)
index 0000000..5dc626f
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import "babel-polyfill";
+import { render } from 'react-dom';
+import SkyquakeRouter from 'widgets/skyquake_container/skyquakeRouter.jsx';
+const config = require('json!../config.json');
+
+let context = require.context('./', true, /^\.\/.*\.jsx$/);
+let router = SkyquakeRouter(config, context);
+let element = document.querySelector('#app');
+
+render(router, element);
+
+
diff --git a/skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagement.jsx b/skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagement.jsx
new file mode 100644 (file)
index 0000000..de5064b
--- /dev/null
@@ -0,0 +1,338 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import PlatformRoleManagementStore from './platformRoleManagementStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import 'style/layout.scss';
+import './platformRoleManagement.scss';
+import {Panel, PanelWrapper} from 'widgets/panel/panel';
+import {InputCollection, FormSection} from 'widgets/form_controls/formControls.jsx';
+
+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 PlatformRoleManagement extends React.Component {
+    constructor(props) {
+        super(props);
+        this.Store = this.props.flux.stores.hasOwnProperty('PlatformRoleManagementStore') ? this.props.flux.stores.PlatformRoleManagementStore : this.props.flux.createStore(PlatformRoleManagementStore);
+        this.state = this.Store.getState();
+        this.actions = this.state.actions;
+        this.Store.getPlatform();
+        this.Store.getUsers();
+    }
+    componentDidUpdate() {
+
+    }
+    componentWillMount() {
+        this.Store.listen(this.updateState);
+    }
+    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)
+    }
+    addProject = () => {
+        this.actions.handleAddProject();
+    }
+    viewProject = (un, index) => {
+        this.actions.viewProject(un, index);
+    }
+    editProject = () => {
+        this.actions.editProject(false);
+    }
+    cancelEditProject = () => {
+        this.actions.editProject(true)
+    }
+    closePanel = () => {
+        this.actions.handleCloseProjectPanel();
+    }
+
+    deleteProject = (e) => {
+        e.preventDefault();
+        e.stopPropagation();
+        this.Store.deleteProject({
+                'name': this.state['name']
+            });
+    }
+    updatePlatform = (e) => {
+        let self = this;
+        e.preventDefault();
+        e.stopPropagation();
+        let platformUsers = self.state.platformUsers;
+        let cleanUsers = this.cleanUsers(platformUsers);
+
+
+        this.Store.updatePlatform({
+                'user': JSON.stringify(platformUsers)
+            }
+        );
+    }
+     cleanUsers(projectUsers) {
+        let cleanUsers = [];
+        //Remove null values from role
+        projectUsers.map((u) => {
+            let cleanRoles = [];
+            u.role && u.role.map((r,i) => {
+                let role = {};
+                if(r.role){
+                    //removing key for rbac-platform
+                    delete r.keys;
+                    cleanRoles.push(r)
+                }
+            });
+           u.role = cleanRoles;
+           cleanUsers.push(u);
+        });
+        return cleanUsers;
+    }
+     evaluateSubmit = (e) => {
+        if (e.keyCode == 13) {
+            if (this.props.isEdit) {
+                this.updatePlatform(e);
+            }
+            e.preventDefault();
+            e.stopPropagation();
+        }
+    }
+    updateSelectedUser = (e) => {
+        this.setState({
+            selected
+        })
+    }
+    addUserToProject = (e) => {
+        this.actions.handleAddUser();
+    }
+    removeUserFromProject = (userIndex, e) => {
+        this.actions.handleRemoveUserFromProject(userIndex);
+    }
+    updateUserRoleInProject = (userIndex, roleIndex, e) => {
+        this.actions.handleUpdateUserRoleInProject({
+            userIndex,
+            roleIndex,
+            value: JSON.parse(e.target.value)
+        })
+    }
+    toggleUserRoleInProject = (userIndex, roleIndex, e) => {
+        this.actions.handleToggleUserRoleInProject({
+            userIndex,
+            roleIndex,
+            checked: JSON.parse(e.currentTarget.checked)
+        })
+    }
+    removeRoleFromUserInProject = (userIndex, roleIndex, e) => {
+        this.actions.handleRemoveRoleFromUserInProject({
+            userIndex,
+            roleIndex
+        })
+    }
+    addRoleToUserInProject = (userIndex, e) => {
+        this.actions.addRoleToUserInProject(userIndex);
+    }
+    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.editProject} />
+            </ButtonGroup>
+        );
+        let platformUsers = [];
+        self.state.platformUsers.map((u) => {
+            platformUsers.push(u['user-name']);
+        });
+
+        if(!this.state.isReadOnly) {
+            formButtonsHTML = (
+                                state.isEdit ?
+                                (
+                                    <ButtonGroup className="buttonGroup">
+                                        <Button label="Update" type="submit" onClick={this.updatePlatform} />
+                                        <Button label="Delete" onClick={this.deleteProject} />
+                                        <Button label="Cancel" onClick={this.cancelEditProject} />
+                                    </ButtonGroup>
+                                )
+                                : (
+                                    <ButtonGroup className="buttonGroup">
+                                        <Button label="Edit" type="submit" onClick={this.updatePlatform}  />
+                                    </ButtonGroup>
+                                )
+                            )
+        }
+
+        html = (
+            <PanelWrapper column>
+                <AppHeader nav={[{name: 'USER MANAGEMENT', onClick: this.context.router.push.bind(this, {pathname: '/'})}]}/>
+                <PanelWrapper className={`row projectManagement ${false ? 'projectList-open' : ''}`} style={{'alignContent': 'center', 'flexDirection': 'row'}} >
+                    <PanelWrapper onKeyUp={this.evaluateSubmit}
+                        className={`ProjectAdmin column`}>
+                        <Panel
+                            title="Manage Roles"
+                            style={{marginBottom: 0}}
+                            no-corners>
+                            <FormSection title="USER ROLES">
+
+                            <table>
+                                <thead>
+                                    <tr>
+                                        {!state.isReadOnly ? <td></td> : null}
+                                        <td>User Name</td>
+                                        {
+                                            state.roles.map((r,i) => {
+                                                return <td key={i}>{r}</td>
+                                            })
+                                        }
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                    {
+                                state.platformUsers.map((u,i)=> {
+                                    let userRoles = u.role && u.role.map((r) => {
+                                        return r.role;
+                                    }) || [];
+                                    return (
+                                        <tr key={i}>
+                                            {!state.isReadOnly ? <td><span
+                                                                        className="removeInput"
+                                                                        onClick={self.removeUserFromProject.bind(self, u)}
+                                                                    >
+                                                                        <img src={imgRemove} />
+
+                                                                    </span></td> : null}
+                                            <td>
+                                                {u['user-name']}
+                                            </td>
+                                            {
+                                                state.roles.map((r,j) => {
+                                                    return <td key={j}><Input readonly={state.isReadOnly} type="checkbox" onChange={self.toggleUserRoleInProject.bind(self, i, j)} checked={(userRoles.indexOf(r) > -1)} /></td>
+                                                })
+                                            }
+                                        </tr>
+                                    )
+                                })
+                            }
+                                </tbody>
+                            </table>
+                                {
+                                    !state.isReadOnly ?
+                                        <div className="tableRow tableRow--header">
+                                            <div>
+                                                <div className="addUser">
+                                                    <SelectOption
+                                                        onChange={this.actions.handleSelectedUser}
+                                                        defaultValue={state.selectedUser}
+                                                        initial={true}
+                                                        options={state.users && state.users.filter((u) => {
+                                                            return platformUsers.indexOf(u['user-name']) == -1
+                                                        }).map((u) => {
+                                                            return {
+                                                                label: u['user-name'],
+                                                                value: u
+                                                            }
+                                                        })}
+                                                    />
+                                                    <span className="addInput" onClick={this.addUserToProject}><img src={imgAdd} />
+                                                        Add User
+                                                    </span>
+                                                </div>
+                                            </div>
+                                        </div> : null
+                                }
+
+                            </FormSection>
+                        </Panel>
+                        {formButtonsHTML}
+                    </PanelWrapper>
+                </PanelWrapper>
+            </PanelWrapper>
+        );
+        return html;
+    }
+}
+// onClick={this.Store.update.bind(null, Account)}
+PlatformRoleManagement.contextTypes = {
+    router: React.PropTypes.object
+};
+
+PlatformRoleManagement.defaultProps = {
+    projectList: [],
+    selectedProject: {}
+}
+
+export default SkyquakeComponent(PlatformRoleManagement);
+
+
+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/>)
+    }
+}
+
+
+
+
diff --git a/skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagement.scss b/skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagement.scss
new file mode 100644 (file)
index 0000000..dc8fcce
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+/* If there's time this really needs to be rewritten. Ideally with smooth animations.*/
+@import "style/_colors.scss";
+
+.projectManagement {
+        max-width: 900px;
+
+    .skyquakePanel-wrapper {
+        overflow-x: hidden;
+    }
+    .projectList {
+
+        -ms-flex: 0 1 200px;
+        flex: 0 1 200px;
+
+        .activeUser {
+            font-weight:bold;
+        }
+
+        /* transition: all 2s;*/
+        &.expanded {
+            -ms-flex: 1 1 100%;
+            flex: 1 1 100%;
+            /* transition: all 300ms;*/
+            .tableRow>div:not(.projectName) {
+                opacity: 1;
+                /* width:auto;*/
+                /* transition: width 600ms;*/
+                /* transition: opacity 300ms;*/
+            }
+            &.collapsed {
+                -ms-flex: 0 1 200px;
+                flex: 0 1 200px;
+                /* transition: all 2s;*/
+                .tableRow>div:not(.projectName) {
+                    /* opacity: 0;*/
+                    /* width:0px;*/
+                    display:none;
+                    overflow:hidden;
+                    /* transition: all 600ms;*/
+                }
+            }
+        }
+        &.hideColumns {
+            overflow:hidden;
+            >div {
+                overflow:hidden;
+            }
+            .tableRow>div:not(.projectName) {
+                width: 0px;
+                /* transition: all 600ms;*/
+            }
+            .projectName {
+                &--header {
+                    /* display:none;*/
+             }
+            }
+        }
+        .projectName {
+            cursor:pointer;
+        }
+
+
+    }
+
+    .projectAdmin {
+            -ms-flex: 1 1;
+            flex: 1 1;
+            width:auto;
+            opacity:1;
+
+        textarea{
+            height: 100px;
+        }
+    }
+    &.projectList-open {
+        .projectAdmin {
+            -ms-flex: 0 1 0px;
+                flex: 0 1 0px;
+            opacity:0;
+            /* width: 0px;*/
+            display:none;
+            /* transition: opacity 300ms;*/
+            /* transition: width 600ms;*/
+
+        }
+    }
+    .buttonGroup {
+        margin: 0 0.5rem 0.5rem;
+        background: #ddd;
+        padding-bottom: 0.5rem;
+        padding: 0.5rem 0;
+        border-top: #d3d3d3 1px solid;
+    }
+    .addUser {
+        display:-ms-flexbox;
+        display:flex;
+        -ms-flex-direction:row;
+            flex-direction:row;
+        label {
+            -ms-flex: 0 1;
+                flex: 0 1;
+                width:150px;
+            select {
+                width:150px;
+            }
+        }
+    }
+    .projectUsers {
+        .userName {
+            -ms-flex-pack: start;
+            justify-content: flex-start;
+            padding-top: 0.75rem;
+        }
+        select {
+            margin-bottom:0.5rem;
+        }
+        .addRole {
+            margin:.25rem 0;
+        }
+        .buttonGroup {
+            display:-ms-flexbox;
+            display:flex;
+        }
+
+    }
+    .projectUsers.tableRow--data:hover {
+            background:none;
+            color: black;
+        }
+
+    table {
+        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;
+            }
+        }
+    }
+}
+
+
+
+.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 {
+
+    }
+}
+.tableRow {
+    display:-ms-flexbox;
+    display:flex;
+    -ms-flex-wrap: nowrap;
+    flex-wrap: nowrap;
+    padding: 0.25rem;
+    margin: .125rem 0;
+    >div {
+        padding: 0.25rem;
+        -ms-flex: 1 1 33%;
+            flex: 1 1 33%;
+        display: -ms-flexbox;
+        display: flex;
+        -ms-flex-direction: column;
+            flex-direction: column;
+        -ms-flex-pack: center;
+            justify-content: center;
+    }
+    &--header {
+        font-weight:bold;
+    }
+    &--data {
+        &:hover:not(&-active) {
+            background:$neutral-dark-1;
+        }
+        &:hover, .activeUser, &-active{
+            cursor:pointer;
+            color:white;
+        }
+        .activeUser, &-active{
+            background: #00acee;
+        }
+    }
+}
+
+.addInput, .removeInput {
+    display:-ms-flexbox;
+    display:flex;
+    -ms-flex-align:center;
+    align-items:center;
+    margin-left: 1rem;
+
+    font-size:0.75rem;
+    text-transform:uppercase;
+    font-weight:bold;
+
+    cursor:pointer;
+    img {
+        height:0.75rem;
+        margin-right:0.5rem;
+        width:auto;
+    }
+    span {
+        color: #5b5b5b;
+        text-transform: uppercase;
+    }
+}
diff --git a/skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementActions.js b/skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementActions.js
new file mode 100644 (file)
index 0000000..6ef5720
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+module.exports = function(Alt) {
+   return Alt.generateActions(
+                                       'handleUpdateInput',
+                                       'handleAddProjectItem',
+                                       'handleRemoveProjectItem',
+                                       'handleUpdateProjectRole',
+                                       'viewProject',
+                                       'editProject',
+                                       'handleCloseProjectPanel',
+                                       'handleHideColumns',
+                                       'handleSelectedUser',
+                                       'handleSelectedRole',
+                                       'handleAddUser',
+                                       'handleRemoveUserFromProject',
+                                       'getProjectsSuccess',
+                                       'getPlatformSuccess',
+                                       'getPlatformRoleUsersSuccess',
+                                       'getProjectsNotification',
+                                       'handleDisabledChange',
+                                       'handlePlatformRoleUpdate',
+                                       'handleAddProject',
+                                       'handleCreateProject',
+                                       'handleUpdateProject',
+                                       'handleUpdateSelectedUser',
+                                       'handleUpdateUserRoleInProject',
+                                       'handleToggleUserRoleInProject',
+                                       'addRoleToUserInProject',
+                                       'handleRemoveRoleFromUserInProject',
+                                       'updateProjectSuccess',
+                                       'createProjectSuccess',
+                                       'deleteProjectSuccess'
+                                       );
+}
diff --git a/skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementSource.js b/skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementSource.js
new file mode 100644 (file)
index 0000000..724312e
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * 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.getPlatformRoleUsersSuccess,
+                    loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        getPlatform: {
+          remote: function() {
+              return new Promise(function(resolve, reject) {
+                $.ajax({
+                  url: `/platform?api_server=${API_SERVER}`,
+                  type: 'GET',
+                  beforeSend: Utils.addAuthorizationStub,
+                  success: function(data, textStatus, jqXHR) {
+                    resolve(data.platform);
+                  }
+                }).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.getPlatformSuccess,
+                    loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        updatePlatform: {
+          remote: function(state, project) {
+            return new Promise(function(resolve, reject) {
+              $.ajax({
+                  url: `/platform?api_server=${API_SERVER}`,
+                  type: 'PUT',
+                  data: project,
+                  dataType: 'json',
+                  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 project.'
+          }),
+          success: Alt.actions.global.updateProjectSuccess,
+          loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        deleteProject: {
+          remote: function(state, project) {
+            return new Promise(function(resolve, reject) {
+              $.ajax({
+                url: `/project/${project['name']}?api_server=${API_SERVER}`,
+                type: 'DELETE',
+                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.deleteProjectSuccess,
+          loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        createProject: {
+            remote: function(state, project) {
+
+              return new Promise(function(resolve, reject) {
+                $.ajax({
+                  url: `/project?api_server=${API_SERVER}`,
+                  type: 'POST',
+                  data: project,
+                  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.createProjectSuccess,
+            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/platformRoleManagement/platformRoleManagementStore.js b/skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementStore.js
new file mode 100644 (file)
index 0000000..ef03b9d
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import PlatformRoleManagementActions from './platformRoleManagementActions.js';
+import PlatformRoleManagementSource from './platformRoleManagementSource.js';
+import _ from 'lodash';
+export default class PlatformRoleManagementStore {
+    constructor() {
+        this.actions = PlatformRoleManagementActions(this.alt);
+        this.bindActions(this.actions);
+        this.registerAsync(PlatformRoleManagementSource);
+        this.projects = [];
+        this['name'] = '';
+        this['description'] = 'Some Description';
+        this.platformUsers = [];
+        this.selectedUser = null;
+        this.selectedRole = null;
+        this.roles = ['rw-rbac-platform:platform-admin', 'rw-rbac-platform:platform-oper', 'rw-rbac-platform:super-admin'
+        // 'some_other_role', 'yet_another_role', 'operator_role', 'some_other_role', 'yet_another_role'
+        ];
+        this.users = [];
+        this.activeIndex = null;
+        this.isReadOnly = true;
+        this.projectOpen = 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
+        });
+    }
+    viewProject(data) {
+        let project = data[0];
+        let projectIndex = data[1];
+
+        let ProjectUser = {
+            'name': project['name'],
+            'description': project['description'],
+            'platformUsers': project['project-config'] && project['project-config']['user'] || []
+        }
+        let state = _.merge({
+            activeIndex: projectIndex,
+            projectOpen: true,
+            isEdit: true,
+            isReadOnly: true
+        }, ProjectUser);
+        this.setState(state)
+    }
+    editProject(isEdit) {
+        this.setState({
+            isReadOnly: isEdit
+        })
+    }
+    handleCloseProjectPanel() {
+        this.setState({
+            projectOpen: false,
+            isEdit: false,
+            isReadOnly: true
+        })
+    }
+    handleHideColumns(e) {
+        if(this.projectOpen && 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
+        })
+    }
+    handleSelectedUser(event) {
+        this.setState({
+            selectedUser: JSON.parse(event.currentTarget.value)
+        })
+    }
+
+    handleSelectedRole(event) {
+        this.setState({
+            selectedRole: JSON.parse(event.currentTarget.value)
+        })
+    }
+    resetProject() {
+        let name = '';
+        let description = '';
+        return {
+            'name' : name,
+            'description' : description
+        }
+    }
+    handleAddProject() {
+        this.setState(_.merge( this.resetProject() ,
+              {
+                isEdit: false,
+                projectOpen: true,
+                activeIndex: null,
+                isReadOnly: false,
+                platformUsers: []
+            }
+        ))
+    }
+
+    handleUpdateSelectedUser(user) {
+        this.setState({
+            selectedUser: JSON.parse(user)
+        });
+    }
+    handleAddUser() {
+        let u = JSON.parse(this.selectedUser);
+        let r = this.selectedRole;
+        let platformUsers = this.platformUsers;
+        console.log('adding user')
+        platformUsers.push({
+          'user-name': u['user-name'],
+          'user-domain': u['user-domain'],
+          "role":[{
+                      "role": r
+            }
+          ]
+        })
+        this.setState({platformUsers, selectedUser: null})
+    }
+    handleToggleUserRoleInProject(data) {
+        let self = this;
+        let {userIndex, roleIndex, checked} = data;
+        let platformUsers = this.platformUsers;
+        let selectedRole = self.roles[roleIndex];
+        if(checked) {
+            if(!platformUsers[userIndex].role) platformUsers[userIndex].role = [];
+            platformUsers[userIndex].role.push({
+                role: selectedRole
+            })
+        } else {
+            let role = platformUsers[userIndex].role;
+            platformUsers[userIndex].role.splice(_.findIndex(role, function(r) { return r.role == selectedRole; }), 1)
+        }
+       self.setState({platformUsers});
+
+    }
+    handleUpdateUserRoleInProject(data) {
+        let {userIndex, roleIndex, value} = data;
+        let platformUsers = this.platformUsers;
+        platformUsers[userIndex].role[roleIndex].role = value;
+
+    }
+    addRoleToUserInProject(userIndex) {
+        let platformUsers = this.platformUsers;
+        if(!platformUsers[userIndex].role) {
+            platformUsers[userIndex].role = [];
+        }
+        platformUsers[userIndex].role.push({
+              'role': null
+            });
+        this.setState({
+            platformUsers
+        })
+    }
+    handleRemoveRoleFromUserInProject (data) {
+        let {userIndex, roleIndex} = data;
+        let platformUsers = this.platformUsers;
+        platformUsers[userIndex].role.splice(roleIndex, 1);
+        this.setState({
+            platformUsers
+        })
+    }
+    handleRemoveUserFromProject (userIndex) {
+        let platformUsers = this.platformUsers;
+        platformUsers.splice(userIndex, 1);
+        this.setState({
+            platformUsers
+        })
+    }
+    getProjectsSuccess(projects) {
+        this.alt.actions.global.hideScreenLoader.defer();
+        this.setState({projects: projects});
+    }
+    getPlatformSuccess(platform) {
+        this.alt.actions.global.hideScreenLoader.defer();
+        let platformUsers = platform && platform.user || [];
+        let state = _.merge({
+            platform: platform,
+            projectOpen: true,
+            isEdit: true,
+            isReadOnly: true,
+            platformUsers: platformUsers
+        });
+        this.setState(state)
+    }
+    getPlatformRoleUsersSuccess(users) {
+        console.log(users)
+        this.alt.actions.global.hideScreenLoader.defer();
+        this.setState({users});
+    }
+    updateProjectSuccess() {
+        this.alt.actions.global.hideScreenLoader.defer();
+        let projects = this.projects || [];
+        projects[this.activeIndex] = {
+            'name': this['name'],
+            'description': this['description']
+        }
+        this.setState({
+            projects,
+            isEdit: true,
+            isReadOnly: true
+        })
+    }
+    deleteProjectSuccess() {
+        this.alt.actions.global.hideScreenLoader.defer();
+        let projects = this.projects;
+        projects.splice(this.activeIndex, 1);
+        this.setState({projects, projectOpen: false})
+    }
+    createProjectSuccess() {
+        this.alt.actions.global.hideScreenLoader.defer();
+        let projects = this.projects || [];
+        projects.push({
+            'name': this['name'],
+            'description': this['description']
+         });
+        let newState = {
+            projects,
+            isEdit: true,
+            isReadOnly: true,
+            activeIndex: projects.length - 1
+        };
+        _.merge(newState)
+        this.setState(newState);
+    }
+}
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..a786858
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+ * 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);
+    }
+    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;
+        const User = this.context.userProfile || {};
+        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>
+        );
+        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>
+                            <FormSection title="USER INFO">
+                                <table className="userProfile-table">
+                                    <thead>
+                                        <tr>
+                                            <td>Project</td>
+                                            <td>Role</td>
+                                        </tr>
+                                    </thead>
+                                    <tbody>
+                                        {
+                                            User.data && User.data.projectId && User.data.projectId.map((p,i)=> {
+                                                let project = User.data.project[p];
+                                                let projectConfig = project && project.data['project-config'];
+                                                let userRoles = [];
+                                                return (
+                                                    <tr key={i}>
+                                                        <td>
+                                                            {p}
+                                                        </td>
+                                                        <td>
+                                                            {
+                                                                project && Object.keys(project.role).map(function(k) {
+                                                                    return <div>{k}</div>
+                                                                })
+                                                            }
+                                                        </td>
+                                                    </tr>
+                                                )
+                                            })
+                                        }
+                                    </tbody>
+                                </table>
+                            </FormSection>
+                            {passwordSectionHTML}
+
+                        </Panel>
+                        <div  className="buttonSection">
+                            {formButtonsHTML}
+                        </div>
+                    </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);
+    }
+}
diff --git a/skyquake/plugins/user_management/webpack.production.config.js b/skyquake/plugins/user_management/webpack.production.config.js
new file mode 100644 (file)
index 0000000..49ad631
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+var Webpack = require('webpack');
+var path = require('path');
+var nodeModulesPath = path.resolve(__dirname, 'node_modules');
+var buildPath = path.resolve(__dirname, 'public', 'build');
+var mainPath = path.resolve(__dirname, 'src', 'main.js');
+var uiPluginCmakeBuild = process.env.ui_plugin_cmake_build || false;
+var frameworkPath = uiPluginCmakeBuild?'../../../../skyquake/skyquake-build/framework':'../../framework';
+var HtmlWebpackPlugin = require('html-webpack-plugin');
+var CommonsPlugin = new require("webpack/lib/optimize/CommonsChunkPlugin")
+// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
+process.env.UV_THREADPOOL_SIZE=64;
+var config = {
+    devtool: 'source-map',
+    entry: mainPath,
+    output: {
+        path: buildPath,
+        filename: 'bundle.js',
+        publicPath: "build/"
+    },
+    resolve: {
+        extensions: ['', '.js', '.jsx', '.css', '.scss'],
+        root: path.resolve(frameworkPath),
+        alias: {
+            'widgets': path.resolve(frameworkPath) + '/widgets',
+            'style':  path.resolve(frameworkPath) + '/style',
+            'utils':  path.resolve(frameworkPath) + '/utils'
+        }
+    },
+    module: {
+        loaders: [{
+                test: /\.(jpe?g|png|gif|svg|ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/i,
+                loader: "file-loader"
+            },
+            {
+                test: /\.(js|jsx)$/,
+                exclude: /react-treeview/,
+                loader: 'babel-loader',
+                query: {
+                    presets: ["es2015", "stage-0", "react"]
+                }
+            }, {
+                test: /\.css$/,
+                loader: 'style!css'
+            }, {
+                test: /\.scss/,
+                loader: 'style!css!sass?includePaths[]='+ path.resolve(frameworkPath)
+            }
+        ]
+    },
+    plugins: [
+        new HtmlWebpackPlugin({
+            filename: '../index.html'
+            , templateContent: '<div id="app"></div>'
+        }),
+        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+    ]
+};
+module.exports = config;
index 9b52e96..6019357 100644 (file)
@@ -105,9 +105,12 @@ if (cluster.isMaster && clusteredLaunch) {
 
        var app = express();
 
+       app.set('views', __dirname + '/framework/core/views');
+       app.engine('html', require('ejs').renderFile);
+
        app.use(session({
          secret: 'ritio rocks',
-         resave: true,
+         resave: false,
          saveUninitialized: true
        }));
        app.use(bodyParser.json());
@@ -135,6 +138,9 @@ if (cluster.isMaster && clusteredLaunch) {
        var descriptor_routes = require('./framework/core/modules/routes/descriptorModel');
        var configuration_routes = require('./framework/core/modules/routes/configuration');
        var configurationAPI = require('./framework/core/modules/api/configuration');
+       var userManagement_routes = require('./framework/core/modules/routes/userManagement');
+       var projectManagement_routes = require('./framework/core/modules/routes/projectManagement');
+       var session_routes = require('./framework/core/modules/routes/sessions');
        /**
         * Processing when a plugin is added or modified
         * @param {string} plugin_name - Name of the plugin
@@ -156,6 +162,15 @@ if (cluster.isMaster && clusteredLaunch) {
 
        }
 
+       /**
+        * Serve jquery
+        */
+       app.use('/jquery', express.static('./node_modules/jquery/dist/jquery.min.js'));
+       /**
+        * Serve images
+        */
+       app.use('/img', express.static('./framework/style/img'));
+
        /**
         * Start listening on a port
         * @param {string} port - Port to listen on
@@ -214,6 +229,15 @@ if (cluster.isMaster && clusteredLaunch) {
                //Configure descriptor route(s)
                app.use(descriptor_routes);
 
+               //Configure user management route(s)
+               app.use(userManagement_routes);
+
+               //Configure project management route(s)
+               app.use(projectManagement_routes);
+
+               //Configure session route(s)
+               app.use(session_routes);
+
                // app.get('/testme', function(req, res) {
                //      res.sendFile(__dirname + '/index.html');
                // });