Project Management: UI initial pass
authorLaurence Maultsby <laurence.maultsby@riftio.com>
Wed, 15 Mar 2017 19:19:11 +0000 (15:19 -0400)
committerLaurence Maultsby <laurence.maultsby@riftio.com>
Wed, 15 Mar 2017 19:19:11 +0000 (15:19 -0400)
Signed-off-by: Laurence Maultsby <laurence.maultsby@riftio.com>
22 files changed:
skyquake/framework/core/modules/api/projectManagementAPI.js [new file with mode: 0644]
skyquake/framework/core/modules/routes/projectManagement.js [new file with mode: 0644]
skyquake/framework/widgets/form_controls/formControls.jsx [new file with mode: 0644]
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/api/ro.js [deleted file]
skyquake/plugins/user-management/routes.js
skyquake/plugins/user-management/src/dashboard/dashboard.jsx
skyquake/plugins/user-management/src/dashboard/userMgmt.scss
skyquake/skyquake.js

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..d73eb21
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ *
+ *   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.get('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.get('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.get('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.get('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)
+                });
+            };
+        });
+    })
+}
+module.exports = ProjectManagement;
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..a8f9a4d
--- /dev/null
@@ -0,0 +1,71 @@
+
+/*
+ *
+ *   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);
+    });
+});
+
+module.exports = Router;
+
+
+
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}`)
+    }
+}
diff --git a/skyquake/plugins/project-management/CMakeLists.txt b/skyquake/plugins/project-management/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e12d4fd
--- /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(
+  config
+  DEPENDS skyquake
+  SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
+  CONFIGURE_COMMAND echo
+  BUILD_COMMAND
+    ${CMAKE_CURRENT_BINARY_DIR}/config/config-build/scripts/build.sh
+  INSTALL_COMMAND
+    ${CMAKE_CURRENT_SOURCE_DIR}/scripts/install.sh
+    ${CMAKE_CURRENT_BINARY_DIR}/config/config-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..b78e3b2
--- /dev/null
@@ -0,0 +1,14 @@
+{
+    "root": "public",
+    "name": "Project Management",
+    "dashboard": "./dashboard/dashboard.jsx",
+    "order": 1,
+    "priority":1,
+    "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..1d57bf3
--- /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.16.3"
+  }
+}
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..37fa131
--- /dev/null
@@ -0,0 +1,420 @@
+/*
+ * 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 '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'
+
+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();
+        this.Store.deleteProject({
+                'name': this.state['name']
+            });
+    }
+    createProject = (e) => {
+        let self = this;
+        e.preventDefault();
+        e.stopPropagation();
+        let projectUsers = self.state.projectUsers;
+
+        //Remove null values from role
+        projectUsers.map((u) => {
+           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 || ((r.role || r['keys']) && (!r.role || !r['keys']))) {
+                projectUsers.splice(i, 1);
+            } else {
+                return u;
+            }
+           })
+        })
+        this.Store.createProject({
+            'name': self.state['name'],
+            'description': self.state.description,
+            'project-config' : {
+                'user': projectUsers
+            }
+        });
+    }
+    updateProject = (e) => {
+        let self = this;
+        e.preventDefault();
+        e.stopPropagation();
+        let projectUsers = self.state.projectUsers;
+
+        //Remove null values from role
+        projectUsers.map((u) => {
+           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 || ((r.role || r['keys']) && (!r.role || !r['keys']))) {
+                projectUsers.splice(i, 1);
+            } else {
+                return u;
+            }
+           })
+        })
+
+        this.Store.updateProject(_.merge({
+            'name': self.state['name'],
+            'description': self.state.description,
+            'project-config' : {
+                'user': projectUsers
+            }
+        }));
+    }
+     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) => {
+        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)
+        })
+    }
+    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>
+        );
+        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">
+                                projectName
+                            </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 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' : ''}`}
+                                        onClick={self.viewProject.bind(null, u, k)}>
+                                        {u['name']}
+                                    </div>
+                                    <div>
+                                        {u['description']}
+                                    </div>
+
+
+                                </div>
+                            )
+                        })}
+                    </Panel>
+                    <ButtonGroup  className="buttonGroup" style={{margin: '0 0.5rem 0.5rem', background: '#ddd', paddingBottom: '0.5rem'}}>
+                        <Button label="Add Project" onClick={this.addProject} />
+                    </ButtonGroup>
+                </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">
+                        <div className="tableRow tableRow--header">
+                            <div className="projectName">
+                                User Name
+                            </div>
+                            <div>
+                                Role
+                            </div>
+                        </div>
+                            {
+                                state.projectUsers && state.projectUsers.map((u, k) => {
+                                    return (
+                                        <div ref={(el) => this[`project-ref-${k}`] = el} className={`tableRow tableRow--data projectUsers`} key={k}>
+                                            <div className="userName" style={state.isReadOnly ? {paddingTop: '0.25rem'} : {} }>{u['user-name']}</div>
+                                            <div>
+                                                {
+                                                    u.role && u.role.map((r, l) => {
+                                                        return (
+                                                            <div key={l}>
+                                                                <div style={{display: 'flex'}} className="selectRole">
+                                                                    <SelectOption
+                                                                        readonly={state.isReadOnly}
+                                                                        defaultValue={r.role}
+                                                                        options={state.roles}
+                                                                        onChange={self.updateUserRoleInProject.bind(self, k, l)}
+                                                                    />
+                                                                    {!state.isReadOnly ?
+                                                                        <span
+                                                                        className="removeInput"
+                                                                        onClick={self.removeRoleFromUserInProject.bind(self, k, l)}
+                                                                        >
+                                                                            <img src={imgRemove} />
+                                                                            Remove Role
+                                                                        </span>
+                                                                        : null
+                                                                    }
+
+                                                                </div>
+                                                            </div>
+                                                        )
+                                                    })
+                                                }
+                                                {!state.isReadOnly ?
+                                                    <div className="buttonGroup">
+                                                        <span className="addInput addRole" onClick={self.addRoleToUserInProject.bind(self, k)}><img src={imgAdd} />
+                                                            Add Role
+                                                        </span>
+                                                        {
+                                                            (!(u.role && u.role.length)) ?
+                                                                <span
+                                                                    className="removeInput"
+                                                                    onClick={self.removeUserFromProject.bind(self, k)}
+                                                                >
+                                                                    <img src={imgRemove} />
+                                                                    Remove User
+                                                                </span> : null
+                                                        }
+                                                    </div>
+                                                    : null
+                                                }
+                                            </div>
+                                        </div>
+                                    )
+                                })
+                            }
+                            {
+                                !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.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>
+        );
+        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..3c2b58e
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * 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;
+        }
+}
+
+
+
+.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 1rem 0.25rem 0;
+        -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..d0baf61
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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',
+                                       '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..72fc2d3
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * 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;
+}
+
+
+let Projects = mockProjects();
+let Users = mockUsers();
+
+
+module.exports = function(Alt) {
+    return {
+
+        getUsers: {
+          remote: function() {
+              return new Promise(function(resolve, reject) {
+                // setTimeout(function() {
+                //   resolve(Users);
+                // }, 1000);
+                $.ajax({
+                  url: `/user?api_server=${API_SERVER}`,
+                  type: 'GET',
+                  beforeSend: Utils.addAuthorizationStub,
+                  success: function(data, textStatus, jqXHR) {
+                    resolve(data.users);
+                  }
+                }).fail(function(xhr){
+                  //Authentication and the handling of fail states should be wrapped up into a connection class.
+                  Utils.checkAuthentication(xhr.status);
+                });
+              });
+          },
+          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) {
+                // setTimeout(function() {
+                //   resolve(Projects);
+                // }, 1000)
+                $.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);
+                });
+              });
+          },
+          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);
+                });
+            });
+          },
+          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) {
+              // setTimeout(function() {
+              //     resolve(true);
+              // }, 1000)
+              $.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);
+              });
+            });
+          },
+          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) {
+                // setTimeout(function() {
+                //     resolve(true);
+                // }, 1000)
+                $.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);
+                });
+              });
+            },
+            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;
+    }
+  }
+}
+
+function mockProjects() {
+  let data = [];
+  let count = 10;
+  for(let i = 0; i < 3; i++) {
+    data.push({
+      name: `Test Project ${i}`,
+      description: 'Some description',
+      roles: ['Some-Role', 'Some-Other-Role'],
+      users: [
+        {
+          'user-name': 'Some-User',
+          'user-domain': 'system',
+          role: [
+            {
+              'role': 'Some-Role',
+              'key-set' : 'some key'
+            },
+            {
+              'role': 'Some-Other-Role',
+              'key-set' : 'some key'
+            }
+          ]
+        },
+        {
+          'user-name': 'Some-User',
+          'user-domain': 'system',
+          role: [
+            {
+              'role': 'Some-Role',
+              'key-set' : 'some key'
+            }
+          ]
+        }
+      ]
+    })
+  }
+  return data;
+}
+function mockUsers() {
+  let data = [];
+  let count = 10;
+  for(let i = 0; i < 10; i++) {
+    data.push({
+      'user-name': `Tester ${i}`,
+      'user-domain': 'Some Domain',
+      platformRoles: {
+        super_admin: true,
+        platform_admin: false,
+        platform_oper: false
+      },
+      disabled: false,
+      projectRoles: [
+        'Project:Role'
+      ]
+    })
+  }
+  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..92a4395
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import ProjectManagementActions from './projectMgmtActions.js';
+import ProjectManagementSource from './projectMgmtSource.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 = ['Assign a role', 'super_admin'];
+        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'],
+            'projectUsers': 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,
+                projectUsers: []
+            }
+        ))
+    }
+
+    handleUpdateSelectedUser(user) {
+        this.setState({
+            selectedUser: JSON.parse(user)
+        });
+    }
+    handleAddUser() {
+        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": r
+            }
+          ]
+        })
+        this.setState({projectUsers})
+    }
+    handleUpdateUserRoleInProject(data) {
+        let {userIndex, roleIndex, value} = data;
+        let projectUsers = this.projectUsers;
+        projectUsers[userIndex].role[roleIndex].role = value;
+        projectUsers[userIndex].role[roleIndex]['keys'] = value;
+
+    }
+    addRoleToUserInProject(userIndex) {
+        let projectUsers = this.projectUsers;
+        if(!projectUsers[userIndex].role) {
+            projectUsers[userIndex].role = [];
+        }
+        projectUsers[userIndex].role.push({
+              'role': null,
+              //temp until we get actual keys
+              'keys' : 'some key'
+            });
+        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 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/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/api/ro.js b/skyquake/plugins/user-management/api/ro.js
deleted file mode 100644 (file)
index 99fb1d7..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * STANDARD_RIFT_IO_COPYRIGHT
- */
-
-var request = require('request');
-var Promise = require('bluebird');
-var rp = require('request-promise');
-var utils = require('../../../framework/core/api_utils/utils.js');
-var constants = require('../../../framework/core/api_utils/constants.js');
-var _ = require('underscore');
-var RO = {}
-RO.get = function(req) {
-return new Promise(function(resolve, reject) {
-        var self = this;
-        var api_server = req.query["api_server"];
-        var requestHeaders = {};
-        var url = utils.confdPort(api_server) + '/api/running/resource-orchestrator';
-        _.extend(
-            requestHeaders,
-            id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
-                'Authorization': req.get('Authorization')
-            }
-        );
-
-        request({
-                url: url + '?deep',
-                type: 'GET',
-                headers: requestHeaders,
-                forever: constants.FOREVER_ON,
-                rejectUnauthorized: false
-            },
-            function(error, response, body) {
-                var data;
-                if (utils.validateResponse('RO.get', error, response, body, resolve, reject)) {
-                    try {
-                        data = JSON.parse(response.body);
-                        if (!id) {
-                            data = data.collection;
-                        }
-
-                        data = data[objKey]
-                    } catch (e) {
-                        console.log('Problem with "' + type.toUpperCase() + '.get"', e);
-                        var err = {};
-                        err.statusCode = 500;
-                        err.errorMessage = {
-                            error: 'Problem with "' + type.toUpperCase() + '.get": ' + e
-                        }
-                        return reject(err);
-                    }
-                    return resolve({
-                        statusCode: response.statusCode,
-                        data: data
-                    });
-                };
-            });
-    });
-}
-
-RO.update = updateAccount;
-
-function updateAccount(req) {
-    var self = this;
-    var api_server = req.query["api_server"];
-    var data = req.body;
-    var requestHeaders = {};
-    var url = utils.confdPort(api_server) + '/api/config/resource-orchestrator';
-    var method = 'PUT'
-
-    return new Promise(function(resolve, reject) {
-        _.extend(requestHeaders,
-            constants.HTTP_HEADERS.accept.data,
-            constants.HTTP_HEADERS.content_type.data, {
-                'Authorization': req.get('Authorization')
-            });
-        request({
-            url: url,
-            method: method,
-            headers: requestHeaders,
-            forever: constants.FOREVER_ON,
-            rejectUnauthorized: false,
-            json: data,
-        }, function(error, response, body) {
-            if (utils.validateResponse('RO.update', error, response, body, resolve, reject)) {
-                return resolve({
-                    statusCode: response.statusCode,
-                    data: JSON.stringify(response.body)
-                });
-            };
-        });
-    })
-}
-
-module.exports = RO;
index bf5915d..8640a99 100644 (file)
@@ -5,22 +5,7 @@
 var app = require('express').Router();
 var cors = require('cors');
 var utils = require('../../framework/core/api_utils/utils.js')
-var ro = require('./api/ro.js')
  // Begin Accounts API
-     app.get('/resource-orchestrator', cors(), function(req, res) {
-        ro.get(req).then(function(data) {
-            utils.sendSuccessResponse(data, res);
-        }, function(error) {
-            utils.sendErrorResponse(error, res);
-        });
-    });
-    app.put('/resource-orchestrator', cors(), function(req, res) {
-        ro.update(req).then(function(data) {
-            utils.sendSuccessResponse(data, res);
-        }, function(error) {
-            utils.sendErrorResponse(error, res);
-        });
-    })
 
     utils.passThroughConstructor(app);
 
index c997e15..c86d6b1 100644 (file)
@@ -213,7 +213,7 @@ class UserManagementDashboard extends React.Component {
         }
 
         html = (
-            <PanelWrapper className={`row userManagement ${!this.state.userOpen ? 'userList-open' : ''}`} style={{'alignContent': 'center', 'flexDirection': 'row'}} >
+            <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">
index cf384e2..d00be88 100644 (file)
@@ -7,9 +7,6 @@
 
 .userManagement {
         max-width: 900px;
-        -ms-flex-item-align:center;
-            -ms-grid-row-align:center;
-            align-self:center;
 
     .skyquakePanel-wrapper {
         overflow-x: hidden;
index 41ac03b..3fd904d 100644 (file)
@@ -136,6 +136,7 @@ if (cluster.isMaster && clusteredLaunch) {
        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');
        /**
         * Processing when a plugin is added or modified
         * @param {string} plugin_name - Name of the plugin
@@ -218,6 +219,9 @@ if (cluster.isMaster && clusteredLaunch) {
                //Configure user management route(s)
                app.use(userManagement_routes);
 
+               //Configure project management route(s)
+               app.use(projectManagement_routes);
+
                // app.get('/testme', function(req, res) {
                //      res.sendFile(__dirname + '/index.html');
                // });