From: Laurence Maultsby Date: Wed, 15 Mar 2017 19:19:11 +0000 (-0400) Subject: Project Management: UI initial pass X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=9ba0c5e5f242e6e5be9af960f49fc9765b8eb0ed;p=osm%2FUI.git Project Management: UI initial pass Signed-off-by: Laurence Maultsby --- diff --git a/skyquake/framework/core/modules/api/projectManagementAPI.js b/skyquake/framework/core/modules/api/projectManagementAPI.js new file mode 100644 index 000000000..d73eb215c --- /dev/null +++ b/skyquake/framework/core/modules/api/projectManagementAPI.js @@ -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 index 000000000..a8f9a4d11 --- /dev/null +++ b/skyquake/framework/core/modules/routes/projectManagement.js @@ -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 + */ + +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 index 000000000..e50fb7ef1 --- /dev/null +++ b/skyquake/framework/widgets/form_controls/formControls.jsx @@ -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 = ( +
+
+ {this.props.title} +
+
+ {this.props.children} +
+
+ ); + 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 ( + + ) + } + buildSelectOption(initial, options, onChange, v, i) { + return ( + + ); + } + 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 = ( +
+ {props.collection.map((v,i) => { + return ( +
+ {inputType(v, i)} + { + props.readonly ? null : Remove} +
+ ) + })} + { props.readonly ? null : Add} +
+ ); + 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 index 000000000..e12d4fd2f --- /dev/null +++ b/skyquake/plugins/project-management/CMakeLists.txt @@ -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 index 000000000..b78e3b2bb --- /dev/null +++ b/skyquake/plugins/project-management/config.json @@ -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 index 000000000..1d57bf37a --- /dev/null +++ b/skyquake/plugins/project-management/package.json @@ -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 index 000000000..8640a9903 --- /dev/null +++ b/skyquake/plugins/project-management/routes.js @@ -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 index 000000000..c4389c731 --- /dev/null +++ b/skyquake/plugins/project-management/scripts/build.sh @@ -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 index 000000000..af71fc557 --- /dev/null +++ b/skyquake/plugins/project-management/scripts/install.sh @@ -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 index 000000000..eb140b74e --- /dev/null +++ b/skyquake/plugins/project-management/server.js @@ -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 index 000000000..37fa13174 --- /dev/null +++ b/skyquake/plugins/project-management/src/dashboard/dashboard.jsx @@ -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 = ( + +