.DS_Store/
.DS_Store
+._.DS_Store
err.log
out.log
node_modules/
npm-debug.log
fixtures/
.build
+.fuse_*
--- /dev/null
+#!/usr/bin/env bash
+#
+# Copyright 2016,2017 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.
+#
+# Author(s): Jeremy Mordkoff, Lezz Giles
+# Creation Date: 08/29/2016
+#
+#
+
+# BUILD.sh
+#
+# This is a top-level build script for OSM SO or UI
+#
+# Arguments and options: use -h or --help
+#
+# dependencies -- requires sudo rights
+
+MODULE=UI
+
+# Defensive bash programming flags
+set -o errexit # Exit on any error
+trap 'echo ERROR: Command failed: \"$BASH_COMMAND\"' ERR
+set -o nounset # Expanding an unset variable is an error. Variables must be
+ # set before they can be used.
+
+###############################################################################
+# Options and arguments
+
+# There
+params="$(getopt -o h -l install,help --name "$0" -- "$@")"
+if [ $? != 0 ] ; then echo "Failed parsing options." >&2 ; exit 1 ; fi
+
+eval set -- $params
+
+installFromPackages=false
+
+while true; do
+ case "$1" in
+ --install) installFromPackages=true; shift;;
+ -h|--help)
+ echo
+ echo "NAME:"
+ echo " $0"
+ echo
+ echo "SYNOPSIS:"
+ echo " $0 -h|--help"
+ echo " $0 [--install] [PLATFORM_REPOSITORY] [PLATFORM_VERSION]"
+ echo
+ echo "DESCRIPTION:"
+ echo " Prepare current system to run $MODULE. By default, the system"
+ echo " is set up to support building $MODULE; optionally, "
+ echo " $MODULE can be installed from a Debian package repository."
+ echo
+ echo " --install: install $MODULE from package"
+ echo " PLATFORM_REPOSITORY (optional): name of the RIFT.ware repository."
+ echo " PLATFORM_VERSION (optional): version of the platform packages to be installed."
+ echo
+ exit 0;;
+ --) shift; break;;
+ *) echo "Not implemented: $1" >&2; exit 1;;
+ esac
+done
+
+# Turn this on after handling options, so the output doesn't get cluttered.
+set -x # Print commands before executing them
+
+###############################################################################
+# Set up repo and version
+
+PLATFORM_REPOSITORY=${1:-osm-rbac}
+PLATFORM_VERSION=${2:-5.1.3.9999.70283}
+
+###############################################################################
+# Main block
+
+# must be run from the top of a workspace
+cd $(dirname $0)
+
+# enable the right repos
+curl http://repos.riftio.com/public/xenial-riftware-public-key | sudo apt-key add -
+
+# always use the same file name so that updates will overwrite rather than enable a second repo
+sudo curl -o /etc/apt/sources.list.d/rift.list http://buildtracker.riftio.com/repo_file/ub16/${PLATFORM_REPOSITORY}/
+sudo apt-get update
+
+sudo apt install -y --allow-downgrades rw.tools-container-tools=${PLATFORM_VERSION} rw.tools-scripts=${PLATFORM_VERSION}
+
+if $installFromPackages; then
+
+ # Install module and platform from packages
+ sudo -H /usr/rift/container_tools/mkcontainer --modes $MODULE --repo ${PLATFORM_REPOSITORY} --rw-version ${PLATFORM_VERSION}
+
+else
+
+ # Install environment to build module
+ sudo -H /usr/rift/container_tools/mkcontainer --modes $MODULE-dev --repo ${PLATFORM_REPOSITORY} --rw-version ${PLATFORM_VERSION}
+
+ # Build and install module
+ make -j16
+ sudo make install
+
+fi
+
+if [[ $MODULE == SO ]]; then
+ echo "Creating Service ...."
+ sudo /usr/rift/bin/create_launchpad_service
+fi
+
+
# DO NOT add any code before this and DO NOT
# include this file anywhere else
##
-include(rift_submodule)
+include(rift_submodule NO_POLICY_SCOPE)
##
# Submodule specific includes will go here,
${subdirs}
)
+##
+# Only skyquake package contains anything
+##
+rift_set_component_package_fields(
+ skyquake
+ DESCRIPTION "RIFT.ware UI"
+ POST_INSTALL_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/postinst"
+ )
+
##
# This macro adds targets for documentaion, unittests, code coverage and packaging
##
FROM ubuntu:16.04
-RUN apt-get update && apt-get -y install python3 curl build-essential
+RUN apt-get update && apt-get -y install python3 curl build-essential apt-transport-https sudo
RUN curl http://repos.riftio.com/public/xenial-riftware-public-key | apt-key add - && \
- curl -o /etc/apt/sources.list.d/OSM.list http://buildtracker.riftio.com/repo_file/ub16/OSM/ && \
+ curl -o /etc/apt/sources.list.d/rift.list http://buildtracker.riftio.com/repo_file/ub16/OSM/ && \
apt-get update && \
- apt-get -y install rw.toolchain-rwbase \
- rw.toolchain-rwtoolchain \
- rw.core.mgmt-mgmt \
- rw.core.util-util \
- rw.core.rwvx-rwvx \
- rw.core.rwvx-rwdts \
- rw.automation.core-RWAUTO \
- rw.tools-container-tools \
- rw.tools-scripts \
- python-cinderclient \
- libxml2-dev \
- libxslt-dev
+ apt-get -y install \
+ rw.tools-container-tools=5.2.0.0.71033 \
+ rw.tools-scripts=5.2.0.0.71033
-RUN /usr/rift/container_tools/mkcontainer --modes build --modes ext --repo OSM
+RUN /usr/rift/container_tools/mkcontainer --modes UI-dev --repo OSM --rw-version 5.2.0.0.71033
-RUN chmod 777 /usr/rift /usr/rift/usr/share
-
-RUN rm -rf /tmp/npm-cache
+RUN chmod 777 /usr/rift
params.GERRIT_PATCHSET_REVISION,
params.TEST_INSTALL,
params.ARTIFACTORY_SERVER)
-}
--- /dev/null
+#!/bin/bash
+
+#
+# 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.
+#
+
+# Post-install script for packaging
+
+/usr/rift/usr/share/rw.ui/skyquake/scripts/handle_plugin_node_modules
+++ /dev/null
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-env/
-build/
-develop-eggs/
-dist/
-deb_dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*,cover
-.hypothesis/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# IPython Notebook
-.ipynb_checkpoints
-
-# pyenv
-.python-version
-
-# celery beat schedule file
-celerybeat-schedule
-
-# dotenv
-.env
-
-# virtualenv
-venv/
-ENV/
-
-# Spyder project settings
-.spyderproject
-
-# Rope project settings
-.ropeproject
+++ /dev/null
-package:
- @python setup.py --command-packages=stdeb.command bdist_deb > /dev/null 2>&1
+++ /dev/null
-# python-osmclient
-A python client for osm orchestration
-
-# Installation
-
-## Install dependencies
-```bash
-sudo apt-get install python-dev libcurl4-gnutls-dev python-pip libgnutls-dev python-prettytable Â
-sudo pip install pycurl
-```
-
-# Setup
-Set the OSM_HOSTNAME variable to the host of the osm server.
-
-Example
-```bash
-localhost$ export OSM_HOSTNAME=<hostname>:8008
-```
-
-# Examples
-
-## upload vnfd
-```bash
-localhost$ osm upload-package ubuntu_xenial_vnf.tar.gz
-{'transaction_id': 'ec12af77-1b91-4c84-b233-60f2c2c16d14'}
-localhost$ osm vnfd-list
-+--------------------+--------------------+
-| vnfd name | id |
-+--------------------+--------------------+
-| ubuntu_xenial_vnfd | ubuntu_xenial_vnfd |
-+--------------------+--------------------+
-```
-
-## upload nsd
-```bash
-localhost$ osm upload-package ubuntu_xenial_ns.tar.gz
-{'transaction_id': 'b560c9cb-43e1-49ef-a2da-af7aab24ce9d'}
-localhost$ osm nsd-list
-+-------------------+-------------------+
-| nsd name | id |
-+-------------------+-------------------+
-| ubuntu_xenial_nsd | ubuntu_xenial_nsd |
-+-------------------+-------------------+
-```
-## vim-list
-
-```bash
-localhost$ osm vim-list
-+-------------+-----------------+--------------------------------------+
-| ro-account | datacenter name | uuid |
-+-------------+-----------------+--------------------------------------+
-| osmopenmano | openstack-site | 2ea04690-0e4a-11e7-89bc-00163e59ff0c |
-+-------------+-----------------+--------------------------------------+
-```
-
-
-## instantiate ns
-```bash
-localhost$ osm ns-create ubuntu_xenial_nsd testns openstack-site
-{'success': ''}
-localhost$ osm ns-list
-+------------------+--------------------------------------+-------------------+--------------------+---------------+
-| ns instance name | id | catalog name | operational status | config status |
-+------------------+--------------------------------------+-------------------+--------------------+---------------+
-| testns | 6b0d2906-13d4-11e7-aa01-b8ac6f7d0c77 | ubuntu_xenial_nsd | running | configured |
-+------------------+--------------------------------------+-------------------+--------------------+---------------+
-```
-
-# Bash Completion
-python-osmclient uses [click](http://click.pocoo.org/5/). You can setup bash completion by putting this in your .bashrc:
-
- eval "$(_OSM_COMPLETE=source osm)"
-
+++ /dev/null
-from io import BytesIO
-import pycurl
-import json
-import pprint
-import uuid
-from prettytable import PrettyTable
-import time
-
-class OsmAPI():
- def __init__(self,host,upload_port=8443):
- if host is None:
- raise Exception('missing host specifier')
-
- self._user='admin'
- self._password='admin'
- self._host=host
- self._upload_port=upload_port
- self._url = 'https://{}/'.format(self._host)
- self._upload_url = 'https://{}:{}/'.format(self._host.split(':')[0],self._upload_port)
-
- def get_curl_cmd(self,url):
- curl_cmd = pycurl.Curl()
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.URL, self._url + url )
- curl_cmd.setopt(pycurl.SSL_VERIFYPEER,0)
- curl_cmd.setopt(pycurl.SSL_VERIFYHOST,0)
- curl_cmd.setopt(pycurl.USERPWD, '{}:{}'.format(self._user,self._password))
- curl_cmd.setopt(pycurl.HTTPHEADER, ['Accept: application/vnd.yand.data+json','Content-Type": "application/vnd.yang.data+json'])
- return curl_cmd
-
- def get_curl_upload_cmd(self,filename):
- curl_cmd = pycurl.Curl()
- curl_cmd.setopt(pycurl.HTTPPOST,[(('package',(pycurl.FORM_FILE,filename)))])
- curl_cmd.setopt(pycurl.URL, self._upload_url + 'composer/upload?api_server=https://localhost&upload_server=https://' + self._host.split(':')[0])
- curl_cmd.setopt(pycurl.SSL_VERIFYPEER,0)
- curl_cmd.setopt(pycurl.SSL_VERIFYHOST,0)
- curl_cmd.setopt(pycurl.USERPWD, '{}:{}'.format(self._user,self._password))
- return curl_cmd
-
- def get_ns_opdata(self,id):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/operational/ns-instance-opdata/nsr/{}?deep'.format(id))
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- if data.getvalue():
- return json.loads(data.getvalue().decode())
- return None
-
- def get_ns_catalog(self):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/running/nsd-catalog/nsd')
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- if data.getvalue():
- resp = json.loads(data.getvalue().decode())
- return resp
- return {'nsd:nsd': []}
-
- def get_config_agents(self):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/config/config-agent')
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- if data.getvalue():
- resp = json.loads(data.getvalue().decode())
- if 'rw-config-agent:config-agent' in resp:
- return resp['rw-config-agent:config-agent']['account']
- return list()
-
- def delete_config_agent(self,name):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/config/config-agent/account/'+name)
- curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE")
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- pprint.pprint(resp)
-
- def add_config_agent(self,name,account_type,server,user,secret):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/config/config-agent')
- curl_cmd.setopt(pycurl.POST,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
-
- postdata={}
- postdata['account'] = list()
-
- account={}
- account['name'] = name
- account['account-type'] = account_type
- account['juju'] = {}
- account['juju']['user'] = user
- account['juju']['secret'] = secret
- account['juju']['ip-address'] = server
- postdata['account'].append(account)
-
- jsondata=json.dumps(postdata)
- curl_cmd.setopt(pycurl.POSTFIELDS,jsondata)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- pprint.pprint(resp)
-
- def get_ns_instance_list(self):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/running/ns-instance-config')
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- return resp['nsr:ns-instance-config']
-
- def get_vnf_catalog(self):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/running/vnfd-catalog/vnfd')
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- if data.getvalue():
- resp = json.loads(data.getvalue().decode())
- return resp
- return {'vnfd:vnfd': []}
-
- def get_vcs_info(self):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/operational/vcs/info')
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- if data.getvalue():
- resp = json.loads(data.getvalue().decode())
- return resp['rw-base:info']['components']['component_info']
- return list()
-
- def get_vnfr_catalog(self):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('v1/api/operational/vnfr-catalog/vnfr')
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- if data.getvalue():
- resp = json.loads(data.getvalue().decode())
- return resp
- return None
-
- def get_vnf(self,vnf_name):
- vnfs=self.get_vnfr_catalog()
- for vnf in vnfs['vnfr:vnfr']:
- if vnf_name == vnf['name']:
- return vnf
- return None
-
- def get_vnf_monitoring(self,vnf_name):
- vnf=self.get_vnf(vnf_name)
- if vnf is not None:
- if 'monitoring-param' in vnf:
- return vnf['monitoring-param']
- return None
-
- def get_ns_monitoring(self,ns_name):
- ns=self.get_ns(ns_name)
- if ns is None:
- raise Exception('cannot find ns {}'.format(ns_name))
-
- vnfs=self.get_vnfr_catalog()
- if vnfs is None:
- return None
- mon_list={}
- for vnf in vnfs['vnfr:vnfr']:
- if ns['id'] == vnf['nsr-id-ref']:
- if 'monitoring-param' in vnf:
- mon_list[vnf['name']] = vnf['monitoring-param']
-
- return mon_list
-
- def list_key_pair(self):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('v1/api/config/key-pair?deep')
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- if 'nsr:key-pair' in resp:
- return resp['nsr:key-pair']
- return list()
-
- def list_ns_catalog(self):
- resp = self.get_ns_catalog()
- table=PrettyTable(['nsd name','id'])
- for ns in resp['nsd:nsd']:
- table.add_row([ns['name'],ns['id']])
- table.align='l'
- print(table)
-
- def list_ns_instance(self):
- resp = self.get_ns_instance_list()
- table=PrettyTable(['ns instance name','id','catalog name','operational status','config status'])
- if 'nsr' in resp:
- for ns in resp['nsr']:
- nsopdata=self.get_ns_opdata(ns['id'])
- nsr=nsopdata['nsr:nsr']
- table.add_row([nsr['name-ref'],nsr['ns-instance-config-ref'],nsr['nsd-name-ref'],nsr['operational-status'],nsr['config-status']])
- table.align='l'
- print(table)
-
- def get_nsd(self,name):
- resp = self.get_ns_catalog()
- for ns in resp['nsd:nsd']:
- if name == ns['name']:
- return ns
- return None
-
- def get_vnfd(self,name):
- resp = self.get_vnf_catalog()
- for vnf in resp['vnfd:vnfd']:
- if name == vnf['name']:
- return vnf
- return None
-
- def get_ns(self,name):
- resp=self.get_ns_instance_list()
- for ns in resp['nsr']:
- if name == ns['name']:
- return ns
- return None
-
- def instantiate_ns(self,nsd_name,nsr_name,account,vim_network_prefix=None,ssh_keys=None,description='default description',admin_status='ENABLED'):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/config/ns-instance-config/nsr')
- curl_cmd.setopt(pycurl.POST,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
-
- postdata={}
- postdata['nsr'] = list()
- nsr={}
- nsr['id']=str(uuid.uuid1())
-
- nsd=self.get_nsd(nsd_name)
- if nsd is None:
- raise Exception('cannot find nsd {}'.format(nsd_name))
-
- datacenter=self.get_datacenter(account)
- if datacenter is None:
- raise Exception('cannot find datacenter account {}'.format(account))
-
- nsr['nsd']=nsd
- nsr['name']=nsr_name
- nsr['short-name']=nsr_name
- nsr['description']=description
- nsr['admin-status']=admin_status
- nsr['om-datacenter']=datacenter['uuid']
-
- if ssh_keys is not None:
- # ssh_keys is comma separate list
- ssh_keys_format=[]
- for key in ssh_keys.split(','):
- ssh_keys_format.append({'key-pair-ref':key})
-
- nsr['ssh-authorized-key']=ssh_keys_format
-
- if vim_network_prefix is not None:
- for index,vld in enumerate(nsr['nsd']['vld']):
- network_name = vld['name']
- nsr['nsd']['vld'][index]['vim-network-name'] = '{}-{}'.format(vim_network_prefix,network_name)
-
- postdata['nsr'].append(nsr)
- jsondata=json.dumps(postdata)
- curl_cmd.setopt(pycurl.POSTFIELDS,jsondata)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- pprint.pprint(resp)
-
- def delete_nsd(self,nsd_name):
- nsd=self.get_nsd(nsd_name)
- if nsd is None:
- raise Exception('cannot find nsd {}'.format(nsd_name))
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/running/nsd-catalog/nsd/'+nsd['id'])
- curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE")
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- pprint.pprint(resp)
-
- def delete_vnfd(self,vnfd_name):
- vnfd=self.get_vnfd(vnfd_name)
- if vnfd is None:
- raise Exception('cannot find vnfd {}'.format(vnfd_name))
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/running/vnfd-catalog/vnfd/'+vnfd['id'])
- curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE")
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- pprint.pprint(resp)
-
- def terminate_ns(self,ns_name):
- ns=self.get_ns(ns_name)
- if ns is None:
- raise Exception('cannot find ns {}'.format(ns_name))
-
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/config/ns-instance-config/nsr/'+ns['id'])
- curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE")
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- pprint.pprint(resp)
-
- def upload_package(self,filename):
- data = BytesIO()
- curl_cmd=self.get_curl_upload_cmd(filename)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- pprint.pprint(resp)
-
- def add_vim_account(self,name,user_name,secret,auth_url,tenant,mgmt_network,float_ip_pool,account_type='openstack'):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('api/config/cloud')
- curl_cmd.setopt(pycurl.POST,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- vim_account={}
- vim_account['account']={}
-
- vim_account['account']['name'] = name
- vim_account['account']['account-type'] = account_type
- vim_account['account'][account_type] = {}
- vim_account['account'][account_type]['key'] = user_name
- vim_account['account'][account_type]['secret'] = secret
- vim_account['account'][account_type]['auth_url'] = auth_url
- vim_account['account'][account_type]['tenant'] = tenant
- vim_account['account'][account_type]['mgmt-network'] = mgmt_network
- vim_account['account'][account_type]['floating-ip-pool'] = float_ip_pool
-
- jsondata=json.dumps(vim_account)
- curl_cmd.setopt(pycurl.POSTFIELDS,jsondata)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- pprint.pprint(resp)
-
- def list_vim_accounts(self):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('v1/api/operational/datacenters')
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- datacenters=resp['rw-launchpad:datacenters']
- if 'ro-accounts' in datacenters:
- return datacenters['ro-accounts']
- return list()
-
- def get_datacenter(self,name):
- data = BytesIO()
- curl_cmd=self.get_curl_cmd('v1/api/operational/datacenters')
- curl_cmd.setopt(pycurl.HTTPGET,1)
- curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write)
- curl_cmd.perform()
- curl_cmd.close()
- resp = json.loads(data.getvalue().decode())
- datacenters=resp['rw-launchpad:datacenters']
- if 'ro-accounts' in datacenters:
- for roaccount in datacenters['ro-accounts']:
- if not 'datacenters' in roaccount:
- continue
- for datacenter in roaccount['datacenters']:
- if datacenter['name'] == name:
- return datacenter
- return None
-
- def show_ns(self,ns_name):
- resp = self.get_ns_instance_list()
- table=PrettyTable(['attribute','value'])
-
- if 'nsr' in resp:
- for ns in resp['nsr']:
- if ns_name == ns['name']:
- # dump ns config
- for k,v in ns.items():
- table.add_row([k,json.dumps(v,indent=2)])
-
- nsopdata=self.get_ns_opdata(ns['id'])
- nsr_optdata=nsopdata['nsr:nsr']
- for k,v in nsr_optdata.items():
- table.add_row([k,json.dumps(v,indent=2)])
- table.align='l'
- print(table)
-
- def show_ns_scaling(self,ns_name):
- resp = self.get_ns_instance_list()
-
- table=PrettyTable(['instance-id','operational status','create-time','vnfr ids'])
-
- if 'nsr' in resp:
- for ns in resp['nsr']:
- if ns_name == ns['name']:
- nsopdata=self.get_ns_opdata(ns['id'])
- scaling_records=nsopdata['nsr:nsr']['scaling-group-record']
- for record in scaling_records:
- if 'instance' in record:
- instances=record['instance']
- for inst in instances:
- table.add_row([inst['instance-id'],inst['op-status'],time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(inst['create-time'])),inst['vnfrs']])
- table.align='l'
- print(table)
-
- def list_vnfr(self):
- return self.get_vnfr_catalog()
-
- def list_vnf_catalog(self):
- resp = self.get_vnf_catalog()
- table=PrettyTable(['vnfd name','id'])
- for ns in resp['vnfd:vnfd']:
- table.add_row([ns['name'],ns['id']])
- table.align='l'
- print(table)
+++ /dev/null
-import click
-from osmclient.common import OsmAPI
-from prettytable import PrettyTable
-import pprint
-import textwrap
-
-@click.group()
-@click.option('--hostname',default=None,envvar='OSM_HOSTNAME',help='hostname of server. Also can set OSM_HOSTNAME in environment')
-@click.pass_context
-def cli(ctx,hostname):
- if hostname is None:
- print("either hostname option or OSM_HOSTNAME environment variable needs to be specified")
- exit(1)
- ctx.obj=OsmAPI.OsmAPI(hostname)
-
-@cli.command(name='ns-list')
-@click.pass_context
-def ns_list(ctx):
- ctx.obj.list_ns_instance()
-
-@cli.command(name='nsd-list')
-@click.pass_context
-def nsd_list(ctx):
- ctx.obj.list_ns_catalog()
-
-@cli.command(name='vnfd-list')
-@click.pass_context
-def vnfd_list(ctx):
- ctx.obj.list_vnf_catalog()
-
-@cli.command(name='vnf-list')
-@click.pass_context
-def vnf_list(ctx):
- resp=ctx.obj.list_vnfr()
- table=PrettyTable(['vnf name','id','operational status','config Status','mgmt interface','nsr id'])
- if resp is not None:
- for vnfr in resp['vnfr:vnfr']:
- if not 'mgmt-interface' in vnfr:
- vnfr['mgmt-interface'] = {}
- vnfr['mgmt-interface']['ip-address'] = None
- table.add_row([vnfr['name'],vnfr['id'],vnfr['operational-status'],vnfr['config-status'],vnfr['mgmt-interface']['ip-address'],vnfr['nsr-id-ref']])
- table.align='l'
- print(table)
-
-@cli.command(name='vnf-monitoring-show')
-@click.argument('vnf_name')
-@click.pass_context
-def vnf_monitoring_show(ctx,vnf_name):
- resp=ctx.obj.get_vnf_monitoring(vnf_name)
- table=PrettyTable(['vnf name','monitoring name','value','units'])
- if resp is not None:
- for monitor in resp:
- table.add_row([vnf_name,monitor['name'],monitor['value-integer'],monitor['units']])
- table.align='l'
- print(table)
-
-@cli.command(name='ns-monitoring-show')
-@click.argument('ns_name')
-@click.pass_context
-def ns_monitoring_show(ctx,ns_name):
- resp=ctx.obj.get_ns_monitoring(ns_name)
- table=PrettyTable(['vnf name','monitoring name','value','units'])
- if resp is not None:
- for key,val in resp.items():
- for monitor in val:
- table.add_row([key,monitor['name'],monitor['value-integer'],monitor['units']])
- table.align='l'
- print(table)
-
-@cli.command(name='ns-create')
-@click.argument('nsd_name')
-@click.argument('ns_name')
-@click.argument('vim_account')
-@click.option('--admin_status',default='ENABLED',help='administration status')
-@click.option('--ssh_keys',default=None,help='comma separated list of keys to inject to vnfs')
-@click.option('--vim_network_prefix',default=None,help='vim network name prefix')
-@click.pass_context
-def ns_create(ctx,nsd_name,ns_name,vim_account,admin_status,ssh_keys,vim_network_prefix):
- ctx.obj.instantiate_ns(nsd_name,ns_name,vim_network_prefix=vim_network_prefix,ssh_keys=ssh_keys,account=vim_account)
-
-@cli.command(name='ns-delete')
-@click.argument('ns_name')
-@click.pass_context
-def ns_delete(ctx,ns_name):
- ctx.obj.terminate_ns(ns_name)
-
-'''
-@cli.command(name='keypair-list')
-@click.pass_context
-def keypair_list(ctx):
- resp=ctx.obj.list_key_pair()
- table=PrettyTable(['key Name','key'])
- for kp in resp:
- table.add_row([kp['name'],kp['key']])
- table.align='l'
- print(table)
-'''
-
-@cli.command(name='upload-package')
-@click.argument('filename')
-@click.pass_context
-def upload_package(ctx,filename):
- ctx.obj.upload_package(filename)
-
-@cli.command(name='ns-show')
-@click.argument('ns_name')
-@click.pass_context
-def ns_show(ctx,ns_name):
- ctx.obj.show_ns(ns_name)
-
-@cli.command(name='ns-scaling-show')
-@click.argument('ns_name')
-@click.pass_context
-def show_ns_scaling(ctx,ns_name):
- ctx.obj.show_ns_scaling(ns_name)
-
-@cli.command(name='nsd-delete')
-@click.argument('nsd_name')
-@click.pass_context
-def nsd_delete(ctx,nsd_name):
- ctx.obj.delete_nsd(nsd_name)
-
-@cli.command(name='vnfd-delete')
-@click.argument('vnfd_name')
-@click.pass_context
-def nsd_delete(ctx,vnfd_name):
- ctx.obj.delete_vnfd(vnfd_name)
-
-@cli.command(name='config-agent-list')
-@click.pass_context
-def config_agent_list(ctx):
- table=PrettyTable(['name','account-type','details'])
- for account in ctx.obj.get_config_agents():
- table.add_row([account['name'],account['account-type'],account['juju']])
- table.align='l'
- print(table)
-
-@cli.command(name='config-agent-delete')
-@click.argument('name')
-@click.pass_context
-def config_agent_delete(ctx,name):
- ctx.obj.delete_config_agent(name)
-
-@cli.command(name='config-agent-add')
-@click.argument('name')
-@click.argument('account_type')
-@click.argument('server')
-@click.argument('user')
-@click.argument('secret')
-@click.pass_context
-def config_agent_add(ctx,name,account_type,server,user,secret):
- ctx.obj.add_config_agent(name,account_type,server,user,secret)
-
-'''
-@cli.command(name='vim-create')
-@click.argument('name')
-@click.argument('user')
-@click.argument('password')
-@click.argument('auth_url')
-@click.argument('tenant')
-@click.argument('mgmt_network')
-@click.argument('floating_ip_pool')
-@click.option('--account_type',default='openstack')
-@click.pass_context
-def vim_create(ctx,name,user,password,auth_url,tenant,mgmt_network,floating_ip_pool,account_type):
- ctx.obj.add_vim_account(name,user,password,auth_url,tenant,mgmt_network,floating_ip_pool,account_type)
-'''
-
-@cli.command(name='vim-list')
-@click.pass_context
-def vim_list(ctx):
- resp=ctx.obj.list_vim_accounts()
- table=PrettyTable(['ro-account','datacenter name','uuid'])
- for roaccount in resp:
- for datacenter in roaccount['datacenters']:
- table.add_row([roaccount['name'],datacenter['name'],datacenter['uuid']])
- table.align='l'
- print(table)
-
-@cli.command(name='vcs-list')
-@click.pass_context
-def vcs_list(ctx):
- resp=ctx.obj.get_vcs_info()
- table=PrettyTable(['component name','state'])
- for component in resp:
- table.add_row([component['component_name'],component['state']])
- table.align='l'
- print(table)
-
-if __name__ == '__main__':
- cli()
+++ /dev/null
-from setuptools import setup,find_packages
-
-setup(
- name='osmclient',
- version='0.1',
- author='Mike Marchetti',
- author_email='mmarchetti@sandvine.com',
- packages=find_packages(),
- include_package_data=True,
- install_requires=[
- 'Click','prettytable'
- ],
- entry_points='''
- [console_scripts]
- osm=osmclient.scripts.osm:cli
- ''',
-)
+++ /dev/null
-[DEFAULT]
-Depends: python-setuptools, python-pycurl, python-click, python-prettytable
// require('../tests/stories/sshKeyCard');
// require('../tests/stories/button');
// require('../tests/stories/sq-input-slider');
- require('../tests/stories/catalogCard');
+ // require('../tests/stories/catalogCard');
+ require('../tests/stories/FileManager');
+ require('../tests/stories/inputs');
// require as many stories as you need.
}
configure(loadStories, module);
+
+
--- /dev/null
+/*
+ *
+ * Copyright 2017 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.
+ *
+ */
+
+/**
+ * Auth util for use across the api_server.
+ * @module framework/core/api_utils/auth
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var jsonLoader = require('require-json');
+var passport = require('passport');
+var OpenIdConnectStrategy = require('passport-openidconnect').Strategy;
+var BearerStrategy = require('passport-http-bearer').Strategy;
+var OAuth2Strategy = require('passport-oauth2');
+var OAuth2RefreshTokenStrategy = require('passport-oauth2-middleware').Strategy;
+var openidConnectConfig = require('./openidconnect_config.json');
+var _ = require('lodash');
+var constants = require('./constants');
+var utils = require('./utils');
+var request = utils.request;
+var rp = require('request-promise');
+var nodeutil = require('util');
+
+
+var Authorization = function(openidConfig) {
+
+ var self = this;
+
+ self.passport = passport;
+
+ self.openidConnectConfig = openidConnectConfig;
+
+ var refreshStrategy = new OAuth2RefreshTokenStrategy({
+ refreshWindow: constants.REFRESH_WINDOW, // Time in seconds to perform a token refresh before it expires
+ userProperty: 'user', // Active user property name to store OAuth tokens
+ authenticationURL: '/login', // URL to redirect unauthorized users to
+ callbackParameter: 'callback' //URL query parameter name to pass a return URL
+ });
+
+ self.passport.use('main', refreshStrategy);
+
+ var openidConfigPrefix = openidConfig.idpServerProtocol + '://' + openidConfig.idpServerAddress + ':' + openidConfig.idpServerPortNumber;
+
+ self.openidConnectConfig.authorizationURL = openidConfigPrefix + self.openidConnectConfig.authorizationURL;
+ self.openidConnectConfig.tokenURL = openidConfigPrefix + self.openidConnectConfig.tokenURL;
+ self.openidConnectConfig.callbackURL = openidConfig.callbackServerProtocol + '://' + openidConfig.callbackAddress + ':' + openidConfig.callbackPortNumber + self.openidConnectConfig.callbackURL;
+
+ var userInfoURL = openidConfigPrefix + self.openidConnectConfig.userInfoURL;
+
+ function SkyquakeOAuth2Strategy(options, verify) {
+ OAuth2Strategy.call(this, options, verify);
+ }
+ nodeutil.inherits(SkyquakeOAuth2Strategy, OAuth2Strategy);
+
+ SkyquakeOAuth2Strategy.prototype.userProfile = function(access_token, done) {
+
+ var requestHeaders = {
+ 'Authorization': 'Bearer ' + access_token
+ };
+
+ request({
+ url: userInfoURL,
+ type: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: constants.REJECT_UNAUTHORIZED
+ }, function(err, response, body) {
+ if (err) {
+ console.log('Error obtaining userinfo: ', err);
+ return done(null, {
+ username: '',
+ subject: ''
+ });
+ } else {
+ if (response.statusCode == constants.HTTP_RESPONSE_CODES.SUCCESS.OK) {
+ try {
+ var data = JSON.parse(response.body);
+ var username = data['preferred_username'];
+ var subject = data['sub'];
+ var domain = data['user_domain'] || 'system';
+ return done(null, {
+ username: username,
+ subject: subject,
+ domain: domain
+ })
+ } catch (ex) {
+ console.log('Error parsing userinfo data');
+ return done(null, {
+ username: '',
+ subject: ''
+ });
+ }
+ }
+ }
+ })
+ };
+
+ var oauthStrategy = new SkyquakeOAuth2Strategy(self.openidConnectConfig,
+ refreshStrategy.getOAuth2StrategyCallback());
+
+ self.passport.use('oauth2', oauthStrategy);
+ refreshStrategy.useOAuth2Strategy(oauthStrategy);
+
+ self.passport.serializeUser(function(user, done) {
+ done(null, user);
+ });
+
+ self.passport.deserializeUser(function(obj, done) {
+ done(null, obj);
+ });
+
+};
+
+Authorization.prototype.configure = function(config) {
+ this.config = config;
+ // Initialize Passport and restore authentication state, if any, from the
+ // session.
+ if (this.config.app) {
+ this.config.app.use(this.passport.initialize());
+ this.config.app.use(this.passport.session());
+ } else {
+ console.log('FATAL error. Bad config passed into authorization module');
+ }
+};
+
+Authorization.prototype.invalidate_token = function(token) {
+
+};
+
+module.exports = Authorization;
constants.SOCKET_POOL_LENGTH = 20;
constants.SERVER_PORT = process.env.SERVER_PORT || 8000;
constants.SECURE_SERVER_PORT = process.env.SECURE_SERVER_PORT || 8443;
+constants.REJECT_UNAUTHORIZED = false;
-constants.BASE_PACKAGE_UPLOAD_DESTINATION = 'upload/packages/';
+constants.BASE_PACKAGE_UPLOAD_DESTINATION = 'upload';
constants.PACKAGE_MANAGER_SERVER_PORT = 4567;
constants.PACKAGE_FILE_DELETE_DELAY_MILLISECONDS = 3 * 1000 * 60; //5 minutes
constants.PACKAGE_FILE_ONBOARD_TRANSACTION_STATUS_CHECK_DELAY_MILLISECONDS = 2 * 1000; //2 seconds
+constants.REFRESH_WINDOW = 10; //Time in seconds to perform a token refresh before it expires
+constants.LAUNCHPAD_ADDRESS = 'localhost';
+constants.LAUNCHPAD_PORT = 8008;
+constants.IDP_SERVER_PROTOCOL = 'https';
+constants.IDP_PORT_NUMBER = 8009;
+constants.CALLBACK_SERVER_PROTOCOL = 'https';
+constants.CALLBACK_PORT_NUMBER = 8443;
+constants.CALLBACK_ADDRESS = 'localhost';
+constants.END_SESSION_PATH = 'end_session';
module.exports = constants;
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2017 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.
+ *
+ */
+
+/**
+ * CSRF util for use across the api_server.
+ * @module framework/core/api_utils/csrf
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var constants = require('./constants.js');
+var utils = require('./utils.js');
+
+var target = null;
+
+function configure(config) {
+ target = config.target;
+}
+
+function csrfCheck(req, res, next) {
+ var host = null;
+
+ if (req.headers.origin != 'null') {
+ host = utils.getHostNameFromURL(req.headers.origin);
+ } else if (req.headers.referer) {
+ host = utils.getHostNameFromURL(req.headers.referer);
+ } else {
+ var msg = 'Request did not contain an origin or referer header. Request terminated.';
+ var error = {};
+ error.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.METHOD_NOT_ALLOWED;
+ error.errorMessage = {
+ error: msg
+ }
+ return utils.sendErrorResponse(error, res);
+ }
+
+ if (!host || host != target) {
+ var msg = 'Request did not originate from authorized source (Potential CSRF attempt). Request terminated.';
+ var error = {};
+ error.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.METHOD_NOT_ALLOWED;
+ error.errorMessage = {
+ error: msg
+ }
+ return utils.sendErrorResponse(error, res);
+ } else {
+ return next();
+ }
+}
+
+module.exports = {
+ configure: configure,
+ csrfCheck: csrfCheck
+};
\ No newline at end of file
--- /dev/null
+{
+ "scope": "openid",
+ "clientID": "cncudWkub3BlbmlkY2xpZW50",
+ "clientSecret": "riftiorocks",
+ "authorizationURL": "/authorization",
+ "tokenURL": "/token",
+ "userInfoURL": "/userinfo",
+ "callbackURL": "/callback"
+}
\ No newline at end of file
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
var sockjs = require('sockjs');
var websocket_multiplex = require('websocket-multiplex');
var utils = require('./utils.js');
+var configurationAPI = require('../modules/api/configuration.js');
var Subscriptions = function() {
if (Type == PollingSocket) {
Socket = new Type(url, req, 1000, req.body);
} else {
- Socket = new Type(url);
+ Socket = new Type(url, ['Bearer', req.session.passport.user.user['access_token']]);
}
console.log('Socket assigned for url', url);
}
self.isClosed = false;
var requestHeaders = {};
_.extend(requestHeaders, {
- 'Authorization': req.get('Authorization')
+ Cookie: req.get('Cookie')
});
var pollServer = function() {
Request({
- url: url,
+ url: utils.projectContextUrl(req, url),
method: config.method || 'GET',
headers: requestHeaders,
json: config.payload,
console.log('Error polling: ' + url);
} else {
if (!self.isClosed) {
- self.poll = setTimeout(pollServer, 1000 || interval);
+ if(process.env.DISABLE_POLLING != "TRUE") {
+ self.poll = setTimeout(pollServer, 1000 || interval);
+ } else {
+ console.log('Polling is disabled. Finishing request.')
+ }
var data = response.body;
if (self.onmessage) {
self.onmessage(data);
return api_server + ':' + CONFD_PORT;
};
+var projectContextUrl = function(req, url) {
+ //NOTE: We need to go into the sessionStore because express-session
+ // does not reliably update the session.
+ // See https://github.com/expressjs/session/issues/450
+ var projectId = (req.session &&
+ req.sessionStore &&
+ req.sessionStore.sessions &&
+ req.sessionStore.sessions[req.session.id] &&
+ JSON.parse(req.sessionStore.sessions[req.session.id])['projectId']) ||
+ (null);
+ if (projectId) {
+ projectId = encodeURIComponent(projectId);
+ return url.replace(/(\/api\/operational\/|\/api\/config\/)(.*)/, '$1project/' + projectId + '/$2');
+ }
+ return url;
+}
+
+var addProjectContextToRPCPayload = function(req, url, inputPayload) {
+ //NOTE: We need to go into the sessionStore because express-session
+ // does not reliably update the session.
+ // See https://github.com/expressjs/session/issues/450
+ var projectId = (req.session &&
+ req.sessionStore &&
+ req.sessionStore.sessions &&
+ req.sessionStore.sessions[req.session.id] &&
+ JSON.parse(req.sessionStore.sessions[req.session.id])['projectId']) ||
+ (null);
+ if (projectId) {
+ if (url.indexOf('/api/operations/')) {
+ inputPayload['project-name'] = projectId;
+ }
+ }
+ return inputPayload;
+}
+
var validateResponse = function(callerName, error, response, body, resolve, reject) {
var res = {};
};
reject(res);
return false;
- } else if (response.statusCode >= 400) {
+ } else if (response.statusCode >= CONSTANTS.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST) {
console.log('Problem with "', callerName, '": ', response.statusCode, ':', body);
res.statusCode = response.statusCode;
// auth specific
- if (response.statusCode == 401) {
+ if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.ERROR.UNAUTHORIZED) {
res.errorMessage = {
error: 'Authentication needed' + body
};
reject(res);
return false;
- } else if (response.statusCode == 204) {
+ } else if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT) {
resolve({
statusCode: response.statusCode,
data: {}
var checkAuthorizationHeader = function(req) {
return new Promise(function(resolve, reject) {
- if (req.get('Authorization') == null) {
+ if (req.session && req.session.authorization == null) {
reject();
} else {
resolve();
reject(res);
fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error: ' + error);
return false;
- } else if (response.statusCode >= 400) {
+ } else if (response.statusCode >= CONSTANTS.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST) {
console.log('Problem with "', callerName, '": ', response.statusCode, ':', body);
res.statusCode = response.statusCode;
// auth specific
- if (response.statusCode == 401) {
+ if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.ERROR.UNAUTHORIZED) {
res.errorMessage = {
error: 'Authentication needed' + body
};
reject(res);
fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error Body: ' + body);
return false;
- } else if (response.statusCode == 204) {
+ } else if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT) {
resolve();
fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Response Body: ' + body);
return false;
* @param {Function} res - a handle to the express response function
*/
var sendErrorResponse = function(error, res) {
+ if (!error.statusCode) {
+ console.error('Status Code has not been set in error object: ', error);
+ }
res.status(error.statusCode);
res.send(error);
}
}
new Promise(function(resolve, reject) {
request({
- uri: uri,
+ uri: projectContextUrl(req, uri),
method: 'GET',
headers: _.extend({}, CONSTANTS.HTTP_HEADERS.accept[type], {
- 'Authorization': req.get('Authorization'),
+ 'Authorization': req.session && req.session.authorization,
forever: CONSTANTS.FOREVER_ON,
rejectUnauthorized: false,
})
}
}
+var buildRedirectURL = function(req, globalConfiguration, plugin, extra) {
+ var api_server = req.query['api_server'] || (req.protocol + '://' + globalConfiguration.get().api_server);
+ var download_server = req.query['dev_download_server'] || globalConfiguration.get().dev_download_server;
+ var url = '/';
+ url += plugin;
+ url += '/?api_server=' + api_server;
+ url += download_server ? '&dev_download_server=' + download_server : '';
+ url += extra || '';
+ return url;
+}
+
+var getHostNameFromURL = function(url) {
+ var match = url.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([^?#]*)(\?[^#]*|)(#.*|)$/);
+ return match && match[3];
+}
+
+var dataToJsonSansPropNameNamespace = function(s) {
+ var a = JSON.parse(s);
+ var b = JSON.stringify(a);
+ var c = b.replace(/{"[-\w]+:/g, '{"');
+ var d = c.replace(/,"[-\w]+:/g, ',"');
+ var j = JSON.parse(d);
+ return j;
+}
+
module.exports = {
/**
* Ensure confd port is on api_server variable.
passThroughConstructor: passThroughConstructor,
- getPortForProtocol: getPortForProtocol
+ getPortForProtocol: getPortForProtocol,
+
+ projectContextUrl: projectContextUrl,
+
+ addProjectContextToRPCPayload: addProjectContextToRPCPayload,
+
+ buildRedirectURL: buildRedirectURL,
+
+ getHostNameFromURL: getHostNameFromURL,
+
+ dataToJsonSansPropNameNamespace: dataToJsonSansPropNameNamespace
};
--- /dev/null
+/*
+ *
+ * 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 Schema = {};
+var request = require('request');
+var Promise = require('promise');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var _ = require('lodash');
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var utils = require('../../api_utils/utils');
+var sessionAPI = require('./sessions.js');
+var configuration = require('./configuration');
+
+var router = require('express').Router();
+
+router.use(bodyParser.json());
+router.use(cors());
+router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+router.get('/app-config', cors(), function (req, res) {
+ getConfig(req).then(function (response) {
+ utils.sendSuccessResponse(response, res);
+ }, function (error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+var inactivityTimeout = process.env.UI_TIMEOUT_SECS || 600000;
+
+var versionPromise = null;
+
+var init = function () {
+ versionPromise = new Promise(
+ function (resolve, reject) {
+ sessionAPI.sessionPromise.then(
+ function (session) {
+ request({
+ url: configuration.getBackendURL() + '/api/operational/version',
+ type: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false
+ },
+ function (error, response, body) {
+ var data;
+ if (utils.validateResponse('schema/version.get', error, response, body, resolve, reject)) {
+ try {
+ data = JSON.parse(response.body)['rw-base:version'];
+ resolve(data.version);
+ } catch (e) {
+ return reject({});
+ }
+ } else {
+ console.log(error);
+ }
+ });
+ });
+ });
+}
+
+var getConfig = function (req) {
+ var api_server = req.query['api_server'];
+
+ var requests = [versionPromise];
+
+ return new Promise(function (resolve, reject) {
+ Promise.all(requests).then(
+ function (results) {
+ var data = {
+ version: results[0],
+ 'api-server': configuration.getBackendURL,
+ 'inactivity-timeout': process.env.UI_TIMEOUT_SECS || 600000
+ }
+ resolve({
+ data: data,
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+ });
+ }).catch(
+ function (error) {
+ var response = {};
+ console.log('Problem with config.get', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to get config' + error
+ };
+ reject(response);
+ });
+ });
+};
+
+module.exports = {
+ getRouter: function () {
+ return router;
+ },
+ init: init
+};
\ No newline at end of file
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
var _ = require('lodash');
var GLOBAL_CONFIGURATION = {
api_server: 'localhost',
- ssl_enabled: true
+ ssl_enabled: true,
+ api_server_port_number: constants.SECURE_SERVER_PORT,
+ idp_server_address: constants.LAUNCHPAD_ADDRESS,
+ idp_server_protocol: constants.IDP_SERVER_PROTOCOL,
+ idp_server_port_number: constants.IDP_PORT_NUMBER
};
/**
return GLOBAL_CONFIGURATION;
};
+var backendURL = null;
+configurationAPI.getBackendURL = function () {
+ if (!backendURL) {
+ backendURL = GLOBAL_CONFIGURATION.api_server_protocol + '://' + GLOBAL_CONFIGURATION.api_server + ':' + GLOBAL_CONFIGURATION.api_server_port_number;
+ }
+ return backendURL;
+}
+
+configurationAPI.getBackendAPI = function () {
+ return configurationAPI.getBackendURL() + '/v2/api';
+}
+
+
+
module.exports = configurationAPI;
uri: utils.confdPort(api_server) + '/api/schema/nsd-catalog/nsd',
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
uri: utils.confdPort(api_server) + '/api/schema/vnfd-catalog/vnfd',
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
--- /dev/null
+/*
+ *
+ * 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 Schema = {};
+var rp = require('request-promise');
+var Promise = require('promise');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var _ = require('lodash');
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var utils = require('../../api_utils/utils');
+var configuration = require('./configuration');
+
+var router = require('express').Router();
+
+
+router.use(bodyParser.json());
+router.use(cors());
+router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+router.get('/model', cors(), function (req, res) {
+ get(req).then(function (response) {
+ utils.sendSuccessResponse(response, res);
+ }, function (error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+router.patch('/model', cors(), function (req, res) {
+ update(req).then(function (response) {
+ utils.sendSuccessResponse(response, res);
+ }, function (error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+router.put('/model', cors(), function (req, res) {
+ add(req).then(function (response) {
+ utils.sendSuccessResponse(response, res);
+ }, function (error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+router.delete('/model', cors(), function (req, res) {
+ remove(req).then(function (response) {
+ utils.sendSuccessResponse(response, res);
+ }, function (error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+module.exports = {
+ getRouter: function () {
+ return router;
+ },
+ init: function () {}
+};
+
+get = function (req) {
+ var backend = configuration.getBackendAPI();
+ var modelPath = req.query['path'];
+ var requestHeaders = _.extend({}, constants.HTTP_HEADERS.accept.collection, {
+ 'Authorization': req.session && req.session.authorization
+ })
+ return new Promise(function (resolve, reject) {
+ Promise.all([
+ rp({
+ uri: backend + '/config' + modelPath,
+ method: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ // }),
+ // rp({
+ // uri: utils.projectContextUrl(req, backend + '/api/operational' + modelPath),
+ // method: 'GET',
+ // headers: requestHeaders,
+ // forever: constants.FOREVER_ON,
+ // rejectUnauthorized: false,
+ // resolveWithFullResponse: true
+ })
+ ]).then(function (results) {
+ var response = {
+ statusCode: results[0].statusCode || 200,
+ data: [null]
+ }
+ if (results[0].body && !results[0].error) {
+ var result = JSON.parse(results[0].body);
+ if (result.collection) {
+ result = result.collection[Object.keys(result.collection)[0]];
+ if (!result.length) {
+ result = null;
+ } else if (result.length === 1) {
+ result = result[0];
+ }
+ }
+ response.data = result;
+ }
+ resolve(response);
+
+ }).catch(function (error) {
+ var res = {};
+ console.log('Problem with model get', error);
+ res.statusCode = error.statusCode || 500;
+ res.errorMessage = {
+ error: 'Failed to get model: ' + error
+ };
+ reject(res);
+ });
+ });
+};
+
+add = function (req) {
+ var backend = configuration.getBackendAPI();
+ var modelPath = req.query['path'];
+ var targetProperty = modelPath.split('/').pop();
+ var newElement = {};
+ var data = {};
+ console.log(req.body);
+ _.forIn(req.body, function (value, key) {
+ if (_.isObject(value)) {
+ if (value.type === 'leaf_empty') {
+ if (value.data) {
+ data[key] = ' ';
+ }
+ } else if (value.type === 'leaf_list') {
+ data[key] = value.data.add;
+ }
+ } else {
+ data[key] = value;
+ }
+ });
+ newElement[targetProperty] = [data];
+ console.log(newElement);
+ var target = backend + '/config' + modelPath;
+ var method = 'POST'
+ var requestHeaders = _.extend({},
+ constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ return new Promise(function (resolve, reject) {
+ rp({
+ uri: target,
+ method: method,
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ json: newElement,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ }).then(function (results) {
+ var response = {};
+ response.data = {
+ path: modelPath,
+ data: data
+ };
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK;
+ console.log(response);
+ resolve(response);
+ }).catch(function (result) {
+ var response = {};
+ var error = {};
+ if (result.error['rpc-reply']) {
+ error.type = result.error['rpc-reply']['rpc-error']['error-tag'];
+ error.message = result.error['rpc-reply']['rpc-error']['error-message'];
+ error.rpcError = result.error['rpc-reply']['rpc-error']
+ } else {
+ error.type = 'api-error';
+ error.message = 'invalid api call';
+ }
+ console.log('Problem with model update', error);
+ response.statusCode = error.statusCode || 500;
+ response.error = error;
+ reject(response);
+ });
+ });
+};
+
+update = function (req) {
+ var backend = configuration.getBackendAPI();
+ var modelPath = req.query['path'];
+ var requestHeaders = _.extend({},
+ constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ var base = backend + '/config' + modelPath + '/';
+
+ function getUpdatePromise(name, value) {
+ var data = {};
+ data[name] = value;
+ return new Promise(function (resolve, reject) {
+ rp({
+ uri: base + name,
+ method: value ? 'PATCH' : 'DELETE',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ json: data,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ }).then(function (result) {
+ resolve({
+ element: name,
+ success: true,
+ value: value
+ });
+ }).catch(function (result) {
+ var error = {};
+ if (result.error['rpc-reply']) {
+ error.type = result.error['rpc-reply']['rpc-error']['error-tag'];
+ error.message = result.error['rpc-reply']['rpc-error']['error-message'];
+ error.rpcError = result.error['rpc-reply']['rpc-error']
+ } else {
+ error.type = 'api-error';
+ error.message = 'invalid api call';
+ }
+ resolve({
+ element: name,
+ success: false,
+ error: error,
+ value: value
+ });
+ })
+ })
+ }
+
+ function getDeletePromise(targetProp, item) {
+ if (item) {
+ targetProp = targetProp + '/' + item;
+ }
+ return getUpdatePromise(targetProp, '');
+ }
+
+ var updates = [];
+ _.forIn(req.body, function (value, key) {
+ var data = {};
+ if (_.isObject(value)) {
+ if (value.type === 'leaf_list') {
+ _.forEach(value.data.remove, function (v) {
+ updates.push(getDeletePromise(key))
+ })
+ _.forEach(value.data.add, function (v) {
+ updates.push(getUpdatePromise(key, v))
+ })
+ } else if (value.type === 'leaf_empty') {
+ if (value.data) {
+ updates.push(getUpdatePromise(key, ' '))
+ } else {
+ updates.push(getDeletePromise(key))
+ }
+ }
+ } else {
+ updates.push(getUpdatePromise(key, value))
+ }
+ })
+
+ return new Promise(function (resolve, reject) {
+ Promise.all(updates).then(function (results) {
+ var response = {};
+ var output = {};
+ var hasError = false;
+ _.forEach(results, function (result) {
+ var record = {};
+ if (output[result.element]) {
+ if (_.isArray(output[result.element].value)) {
+ output[result.element].value.push(result.value);
+ } else {
+ output[result.element].value = [output[result.element].value, result.value];
+ }
+ } else {
+ output[result.element] = result;
+ }
+ hasError = hasError || !result.success
+ })
+ response.data = {
+ result: output,
+ hasError: hasError
+ };
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK;
+ console.log(response);
+ resolve(response);
+ }).catch(function (result) {
+ var response = {};
+ var error = {};
+ if (result.error['rpc-reply']) {
+ error.type = result.error['rpc-reply']['rpc-error']['error-tag'];
+ error.message = result.error['rpc-reply']['rpc-error']['error-message'];
+ error.rpcError = result.error['rpc-reply']['rpc-error']
+ } else {
+ error.type = 'api-error';
+ error.message = 'invalid api call';
+ }
+ console.log('Problem with model update', error);
+ response.statusCode = error.statusCode || 500;
+ response.error = error;
+ reject(response);
+ });
+ });
+};
+
+remove = function (req) {
+ var backend = configuration.getBackendAPI();
+ var modelPath = req.query['path'];
+ var target = backend + '/config' + modelPath;
+ var requestHeaders = _.extend({},
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ })
+ return new Promise(function (resolve, reject) {
+ rp({
+ url: target,
+ method: 'DELETE',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ }).then(function (response) {
+ return resolve({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+ data: modelPath
+ });
+ }).catch(function (result) {
+ var response = {};
+ var error = {};
+ if (result.error['rpc-reply']) {
+ error.type = result.error['rpc-reply']['rpc-error']['error-tag'];
+ error.message = result.error['rpc-reply']['rpc-error']['error-message'];
+ error.rpcError = result.error['rpc-reply']['rpc-error']
+ } else {
+ error.type = 'api-error';
+ error.message = 'invalid api call';
+ }
+ console.log('Problem with model update', error);
+ response.statusCode = error.statusCode || 500;
+ response.error = error;
+ reject(response);
+ });
+ })
+}
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * 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');
+var API_VERSION = 'v2';
+ProjectManagement.get = function(req, fields) {
+ var self = this;
+ var api_server = req.query['api_server'];
+ // by default just load basic info as this request is expensive
+ fields = fields || ['name', 'description', 'project-config'];
+ var select = fields.length ? '?fields=' + fields.join(';') : '';
+
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ rp({
+ uri: `${utils.confdPort(api_server)}/${API_VERSION}/api/operational/project` + select,
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ })
+ ]).then(function(result) {
+ var response = {};
+ response['data'] = {};
+ if (result[0].body) {
+ response['data']['project'] = JSON.parse(result[0].body)['rw-project:project'];
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with ProjectManagement.get', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to get ProjectManagement' + error
+ };
+ reject(response);
+ });
+ });
+};
+
+ProjectManagement.create = function(req) {
+ var self = this;
+ var api_server = req.query['api_server'];
+ var data = req.body;
+ data = {
+ "project":[data]
+ }
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ rp({
+ uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/project',
+ method: 'POST',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ json: data,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ })
+ ]).then(function(result) {
+ var response = {};
+ response['data'] = {};
+ if (result[0].body) {
+ response['data'] = result[0].body;
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with ProjectManagement.create', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to create user' + error
+ };
+ reject(response);
+ });
+ });
+};
+ProjectManagement.update = function(req) {
+ //"rw-project:project"
+ var self = this;
+ var api_server = req.query['api_server'];
+ var bodyData = req.body;
+ // oddly enough, if we do not encode this here letting the request below does so incorrectly
+ var projectName = encodeURIComponent(bodyData.name);
+ var descriptionData = {
+ "rw-project:project" : {
+ "name": bodyData.name,
+ "description": bodyData.description
+ }
+ }
+ var updateTasks = [];
+ var baseUrl = utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/project/' + projectName
+ var updateProjectConfig = rp({
+ uri: baseUrl + '/project-config',
+ method: 'PUT',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ json: {
+ "project-config": bodyData['project-config']
+ },
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ });
+ updateTasks.push(updateProjectConfig);
+
+ var updateProjectDescription = rp({
+ uri: baseUrl + '/description',
+ method: 'PATCH',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ json: {"description": bodyData.description},
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ });
+ updateTasks.push(updateProjectDescription)
+ 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 update project - ' + error
+ };
+ reject(response);
+ });
+ });
+};
+
+ProjectManagement.delete = function(req) {
+ var self = this;
+ var projectname = encodeURIComponent(req.params.projectname);
+ var api_server = req.query["api_server"];
+ var requestHeaders = {};
+ var url = utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/project/' + projectname
+ return new Promise(function(resolve, reject) {
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ rp({
+ url: url,
+ method: 'DELETE',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ }, function(error, response, body) {
+ if (utils.validateResponse('ProjectManagement.DELETE', error, response, body, resolve, reject)) {
+ return resolve({
+ statusCode: response.statusCode,
+ data: JSON.stringify(response.body)
+ });
+ };
+ });
+ })
+}
+
+
+ProjectManagement.getPlatform = function(req, userId) {
+ var self = this;
+ var api_server = req.query['api_server'];
+ var user = req.params['userId'] || userId;
+ return new Promise(function(resolve, reject) {
+ var url = utils.confdPort(api_server) + '/' + API_VERSION + '/api/operational/rbac-platform-config';
+ if(user) {
+ url = url + '/user/' + encodeURIComponent(user);
+ }
+ Promise.all([
+ rp({
+ uri: url,
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ })
+ ]).then(function(result) {
+ var response = {};
+ response['data'] = {};
+ if (result[0].body) {
+ if(user) {
+ response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:user'];
+ } else {
+ response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:rbac-platform-config'];
+ }
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with ProjectManagement.getPlatform', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to get ProjectManagement.getPlatform' + error
+ };
+ reject(response);
+ });
+ });
+};
+
+ProjectManagement.updatePlatform = function(req) {
+ var self = this;
+ var api_server = req.query['api_server'];
+ var bodyData = req.body;
+ data = bodyData;
+ data.user = JSON.parse(data.user)
+ var updateTasks = [];
+
+ var updatePlatform = rp({
+ uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/rbac-platform-config',
+ method: 'PUT',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ json: {
+ "rw-rbac-platform:rbac-platform-config": data
+ },
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ });
+ updateTasks.push(updatePlatform)
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ updateTasks
+ ]).then(function(result) {
+ var response = {};
+ response['data'] = {};
+ if (result[0].body) {
+ response['data'] = result[0].body;
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with ProjectManagement.updatePlatform', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to update platform - ' + error
+ };
+ reject(response);
+ });
+ });
+};
+
+
+module.exports = ProjectManagement;
url: uri + url + '?deep',
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
--- /dev/null
+/*
+ *
+ * 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 Schema = {};
+var rp = require('request-promise');
+var Promise = require('promise');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var _ = require('lodash');
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var utils = require('../../api_utils/utils');
+var configuration = require('./configuration');
+
+var router = require('express').Router();
+
+
+router.use(bodyParser.json());
+router.use(cors());
+router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+router.get('/schema', cors(), function (req, res) {
+ getSchema(req).then(function (response) {
+ utils.sendSuccessResponse(response, res);
+ }, function (error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+module.exports = {
+ getRouter: function () {
+ return router;
+ },
+ init: function () {}
+};
+
+getSchema = function (req) {
+ var schemaURI = configuration.getBackendURL() + '/api/schema/';
+ var schemaPaths = req.query['request'];
+ var paths = schemaPaths.split(',');
+
+ function getSchemaRequest(path) {
+ return rp({
+ uri: schemaURI + path,
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ })
+ }
+
+ var requests = _.map(paths, getSchemaRequest);
+
+ return new Promise(function (resolve, reject) {
+ Promise.all(requests).then(
+ function (results) {
+ var data = {
+ schema: {}
+ }
+ _.forEach(results, function (result, index) {
+ data.schema[paths[index]] = JSON.parse(result.body);
+ });
+ resolve({
+ data: data,
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+ });
+ }).catch(
+ function (error) {
+ var response = {};
+ console.log('Problem with schema.get', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to get schema' + error
+ };
+ reject(response);
+ });
+ });
+};
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * sessions api module. Provides API functions for sessions
+ * @module framework/core/modules/api/sessions
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+"use strict"
+var Promise = require('promise');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var request = utils.request;
+var rp = require('request-promise');
+var sessionsAPI = {};
+var _ = require('lodash');
+var base64 = require('base-64');
+var APIVersion = '/v2';
+var configurationAPI = require('./configuration');
+var UserManagement = require('./userManagementAPI.js');
+var URL = require('url');
+
+// Used for determining what page a user should first go to.
+var Application = {
+ order: [
+ "rw-rbac-platform:super-admin",
+ "rw-rbac-platform:platform-admin",
+ "rw-rbac-platform:platform-oper",
+ "rw-project:project-admin",
+ "rw-project:project-oper",
+ "rw-project-mano:lcm-admin",
+ "rw-project-mano:lcm-oper",
+ "rw-project-mano:catalog-admin",
+ "rw-project-mano:catalog-oper",
+ "rw-project-mano:account-admin",
+ "rw-project-mano:account-oper"
+ ],
+ key: {
+ "rw-rbac-platform:super-admin": "user_management",
+ "rw-rbac-platform:platform-admin": "user_management",
+ "rw-rbac-platform:platform-oper": "user_management",
+ "rw-project:project-admin": "project_management",
+ "rw-project:project-oper": "project_management",
+ "rw-project-mano:catalog-admin": "composer",
+ "rw-project-mano:catalog-oper": "composer",
+ "rw-project-mano:lcm-admin": "launchpad",
+ "rw-project-mano:lcm-oper": "launchpad",
+ "rw-project-mano:account-admin": "accounts",
+ "rw-project-mano:account-oper": "accounts"
+ }
+};
+
+function logAndReject(mesg, reject, errCode) {
+ var res = {};
+ res.errorMessage = {
+ error: mesg
+ }
+ res.statusCode = errCode || constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST;
+ console.log(mesg);
+ reject(res);
+}
+
+function logAndRedirectToLogin(mesg, res, req, invalid) {
+ console.log(mesg);
+ if (!invalid) {
+ res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'login', '&referer=' + encodeURIComponent(req.headers.referer)));
+ }
+ res.end();
+}
+
+function logAndRedirectToEndSession(mesg, res, authorization, url) {
+ console.log(mesg);
+ res.set({
+ 'Authorization': authorization
+ });
+ res.redirect(url);
+ res.end();
+}
+var sessionPromiseResolve = null;
+sessionsAPI.sessionPromise = new Promise(function(resolve, reject) {
+ sessionPromiseResolve = resolve;
+});
+
+sessionsAPI.create = function (req, res) {
+ if (!req.session.passport){
+ logAndRedirectToLogin("lost session", res, req);
+ return new Promise(function (resolve, reject){reject("lost session")});
+ }
+ var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server);
+ var uri = utils.confdPort(api_server);
+ var username = req.session.passport.user['username'];
+ var authorization_header_string = 'Bearer ' + req.session.passport.user.user.access_token;
+ return new Promise(function (resolve, reject) {
+ req.session.authorization = authorization_header_string;
+ req.session.api_server = api_server;
+ req.session.api_protocal = req.protocol;
+ req.session.loggedIn = true;
+ req.session.userdata = {
+ username: username,
+ };
+ UserManagement.getUserInfo(req, req.session.passport.user.username).then(function (results) {
+ var project_list_for_user = null;
+ if (!req.session.projectId && results.data.project) {
+ project_list_for_user = Object.keys(results.data.project);
+ if (project_list_for_user.length > 0) {
+ req.session.projectId = project_list_for_user.sort() && project_list_for_user[0];
+ }
+ }
+ sessionsAPI.setTopApplication(req);
+ req.session.isLCM = results.data.isLCM;
+
+ req.session['ui-state'] = results.data['ui-state'];
+ var lastActiveProject = req.session['ui-state'] && req.session['ui-state']['last-active-project'];
+ if (lastActiveProject) {
+ if (results.data.project.hasOwnProperty(lastActiveProject)) {
+ req.session.projectId = lastActiveProject;
+ }
+
+ }
+
+ var successMsg = 'User => ' + username + ' successfully logged in.';
+ successMsg += req.session.projectId ? 'Project => ' + req.session.projectId + ' set as default.' : '';
+
+ console.log(successMsg);
+
+ req.session.save(function (err) {
+ if (err) {
+ console.log('Error saving session to store', err);
+ }
+ // no response data, just redirect now that session data is set
+ if (req.session['ui-state'] && req.session['ui-state']['last-active-uri']) {
+ var url = URL.parse(req.session['ui-state']['last-active-uri']);
+ var host = req.headers.host;
+ var path = url.path;
+ var hash = url.hash;
+ var protocol = url.protocol;
+ var newUrl = protocol + '//' + host + path + (hash?hash:'');
+ console.log('Redirecting to: ' + newUrl)
+ res.redirect(newUrl)
+ } else {
+ if(req.session.topApplication) {
+ res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, req.session.topApplication));
+ } else {
+ res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'user_management', '#/user-profile'));
+ }
+ }
+ })
+
+ sessionPromiseResolve(req.session);
+
+ }).catch(function (error) {
+ // Something went wrong - Redirect to /login
+ var errorMsg = 'Error logging in or getting list of projects. Error: ' + error;
+ console.log(errorMsg);
+ logAndRedirectToLogin(errorMsg, res, req);
+ });
+ })
+};
+
+sessionsAPI.addProjectToSession = function (req, res) {
+ return new Promise(function (resolve, reject) {
+ if (req.session && req.session.loggedIn == true) {
+ Promise.all([UserManagement.getProfile(req), UserManagement.updateActiveProject(req)]).then(function () {
+ req.session.projectId = req.params.projectId;
+ req.session.topApplication = null;
+ sessionsAPI.setTopApplication(req, req.query.app);
+ req.session.save(function (err) {
+ if (err) {
+ console.log('Error saving session to store', err);
+ var errorMsg = 'Session does not exist or not logged in';
+ logAndReject(errorMsg, reject, constants.HTTP_RESPONSE_CODES.ERROR.NOT_FOUND);
+ } else {
+ var successMsg = 'Added project ' + req.session.projectId + ' to session ' + req.sessionID;
+ console.log(successMsg);
+ var response = {
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+ data: JSON.stringify({
+ status: successMsg
+ })
+ }
+ return resolve(response);
+ }
+ // res.redirect('/');
+ });
+
+ })
+
+ }
+ });
+}
+
+sessionsAPI.delete = function (req, res) {
+ var idpServerAddress = configurationAPI.globalConfiguration.get().idp_server_address;
+ var idpServerProtocol = configurationAPI.globalConfiguration.get().idp_server_protocol;
+ var idpServerPortNumber = configurationAPI.globalConfiguration.get().idp_server_port_number;
+ var idpEndSessionPath = constants.END_SESSION_PATH;
+ var url = idpServerProtocol + '://' +
+ idpServerAddress + ':' +
+ idpServerPortNumber + '/' +
+ idpEndSessionPath;
+ var authorization = req.session.authorization;
+ return new Promise(function (resolve, reject) {
+ Promise.all([
+ UserManagement.updateActiveUri(req),
+ new Promise(function (success, failure) {
+ req.session.destroy(function (err) {
+ if (err) {
+ var errorMsg = 'Error deleting session. Error: ' + err;
+ console.log(errorMsg);
+ success({
+ status: 'error',
+ message: errorMsg
+ });
+ }
+
+ var successMsg = 'Success deleting session';
+ console.log(successMsg);
+
+ success({
+ status: 'success',
+ message: successMsg
+ });
+ });
+ })
+ ]).then(function (result) {
+ // assume the session was deleted!
+ var message = 'Session was deleted. Redirecting to end_session';
+ resolve({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+ data: {
+ url: url,
+ message: message
+ }
+ });
+
+ }).catch(function (error) {
+ var message = "An error occured while deleting session";
+ resolve({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+ data: {
+ url: url,
+ message: message
+ }
+ });
+ });
+ });
+}
+
+sessionsAPI.setTopApplication = function (req, suggestedPlugin) {
+ var selectedProject = req.session.projectId;
+ var userProject = selectedProject ? req.session.projectMap[selectedProject] : null;
+ if (userProject) {
+ if (suggestedPlugin) {
+ if (req.session.platformMap['rw-rbac-platform:super-admin']) {
+ topApplication = suggestedPlugin;
+ } else {
+ var roles = _.reduce(Object.keys(Application.key), function (accumulator, role) {
+ if (Application.key[role] === suggestedPlugin) {
+ accumulator.push(role);
+ }
+ return accumulator;
+ }, []);
+ if (_.some(roles, function (role){return userProject.role[role]})) {
+ req.session.topApplication = suggestedPlugin;
+ return;
+ }
+ }
+ }
+ _.some(Application.order, function (role) {
+ if (userProject.role[role] || req.session.platformMap.role[role]) {
+ req.session.topApplication = Application.key[role];
+ return true;
+ }
+ return false;
+ })
+ }
+}
+
+module.exports = sessionsAPI;
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+// DescriptorModelMeta API (NSD + VNFD)
+
+
+var UserManagement = {};
+var Promise = require('bluebird');
+var rp = require('request-promise');
+var Promise = require('promise');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var _ = require('lodash');
+var ProjectManagementAPI = require('./projectManagementAPI.js');
+var API_VERSION = 'v2';
+
+UserManagement.get = function(req) {
+ var self = this;
+ var api_server = req.query['api_server'];
+
+ return new Promise(function(resolve, reject) {
+ var userConfig = rp({
+ uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/operational/user-config/user',
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ });
+ var userOp = rp({
+ uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/operational/user-state/user',
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ })
+ Promise.all([
+ userConfig,
+ userOp
+ ]).then(function(result) {
+ var response = {};
+ var userConfig = [];
+ var userOpData = {};
+ response['data'] = {};
+ if (result[0].body) {
+ userConfig = JSON.parse(result[0].body)['rw-user:user'];
+ }
+ if (result[1].body) {
+ JSON.parse(result[1].body)['rw-user:user'].map(function(u) {
+ userOpData[u['user-domain'] + ',' + u['user-name']] = u;
+ })
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+ response['data']['user'] = userConfig.map(function(u,i) {
+ var mergedData = _.merge(u, userOpData[u['user-domain'] + ',' + u['user-name']]);
+ mergedData.projects = {
+ ids: [],
+ data: {}
+ };
+ var projects = mergedData.projects;
+ mergedData.role && mergedData.role.map(function(r) {
+ if ((r.role != "rw-project:user-self" )&& (r.role != "rw-rbac-platform:user-self")) {
+ var projectId = r.keys.split(';')[0];
+ if (projectId == "") {
+ projectId = "platform"
+ }
+ if (!projects.data[projectId]) {
+ projects.ids.push(projectId);
+ projects.data[projectId] = [];
+ }
+ projects.data[projectId].push(r.role);
+ }
+ })
+ return mergedData;
+ })
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with UserManagement.get', error);
+ response.statusCode = error.statusCode || constants.HTTP_RESPONSE_CODES.ERROR.INTERNAL_SERVER_ERROR;
+ response.errorMessage = {
+ error: 'Failed to get UserManagement' + error
+ };
+ reject(response);
+ });
+ });
+};
+
+
+UserManagement.getProfile = function(req) {
+ var self = this;
+ var api_server = req.query['api_server'];
+ return new Promise(function(resolve, reject) {
+ var response = {};
+ try {
+ var userId = req.session.userdata.username
+ response['data'] = {
+ userId: userId,
+ projectId: req.session.projectId,
+ domain: req.session.passport.user.domain
+ };
+ UserManagement.getUserInfo(req, userId).then(function(result) {
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK;
+ response.data.data = result.data
+ resolve(response);
+ }, function(error) {
+ console.log('Error retrieving getUserInfo');
+ response.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.INTERNAL_SERVER_ERROR;
+ reject(response);
+ })
+ } catch (e) {
+ var response = {};
+ console.log('Problem with UserManagement.get', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to get UserManagement' + error
+ };
+ reject(response);
+ }
+ });
+};
+UserManagement.getUserInfo = function(req, userId, domain) {
+ var self = this;
+ var api_server = req.query['api_server'];
+ var id = req.params['userId'] || userId;
+ var domain = req.params['domainId'] || domain;
+ var response = {};
+ return new Promise(function(resolve, reject) {
+ if (id) {
+ var getProjects = ProjectManagementAPI.get(req, ['name', 'project-config']);
+ var getPlatformUser = ProjectManagementAPI.getPlatform(req, id);
+ var getUserUiState = UserManagement.getUserUiState(req);
+ Promise.all([
+ getProjects,
+ getPlatformUser,
+ getUserUiState
+ ]).then(function(result) {
+ var userData = {
+ platform: {
+ role: {
+
+ }
+ },
+ //id/key values for each project
+ projectId:[],
+ project: {
+ /**
+ * [projectId] : {
+ * data: [project object],
+ * role: {
+ * [roleId]: true
+ * }
+ * }
+ */
+ }
+ }
+ //Build UI state
+ var uiState = result[2].data && result[2].data['rw-user:user'];
+ userData['ui-state'] = uiState['ui-state'];
+ //Build platform roles
+ var platformRoles = result[1].data.platform && result[1].data.platform.role;
+ platformRoles && platformRoles.map(function(r) {
+ userData.platform.role[r.role] = true
+ });
+ //Build project roles
+ var projects = result[0].data.project;
+ var userProjects = [];
+ projects && projects.map(function(p, i) {
+ userData.project[p.name] = {
+ data: p,
+ role: {}
+ }
+ userData.projectId.push(p.name);
+ if (userData.platform.role['rw-rbac-platform:super-admin']) {
+ userData.project[p.name] = {
+ data: p,
+ role: {
+ "rw-project:project-admin": true,
+ "rw-project:project-oper": true,
+ "rw-project-mano:account-admin": true,
+ "rw-project-mano:account-oper": true,
+ "rw-project-mano:catalog-admin": true,
+ "rw-project-mano:catalog-oper": true,
+ "rw-project-mano:lcm-admin": true,
+ "rw-project-mano:lcm-oper": true
+ }
+ }
+ } else {
+ var users = p['project-config'] && p['project-config'].user;
+ users && users.map(function(u) {
+ if(u['user-name'] == id) {
+ u.role && u.role.map(function(r) {
+ userData.project[p.name].role[r.role] = true;
+ if (r.role === 'rw-project:project-admin') {
+ userData.project[p.name].role["rw-project-mano:account-admin"] = true;
+ userData.project[p.name].role["rw-project-mano:catalog-admin"] = true;
+ userData.project[p.name].role["rw-project-mano:lcm-admin"] = true;
+ userData.isLCM = true;
+ } else if (r.role === 'rw-project:project-oper') {
+ userData.project[p.name].role["rw-project-mano:account-oper"] = true;
+ userData.project[p.name].role["rw-project-mano:catalog-oper"] = true;
+ userData.project[p.name].role["rw-project-mano:lcm-oper"] = true;
+ userData.isLCM = true;
+ }
+ });
+ u["rw-project-mano:mano-role"] && u["rw-project-mano:mano-role"] .map(function(r) {
+ userData.project[p.name].role[r.role] = true;
+ if (r.role.indexOf('rw-project-mano:lcm') > -1) {
+ userData.isLCM = true;
+ }
+ });
+ }
+ })
+ }
+ });
+ response.data = userData;
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK;
+
+ req.session.projectMap = userData.project;
+ req.session.platformMap = userData.platform;
+ resolve(response);
+ })
+ } else {
+ var errorMsg = 'userId not specified in UserManagement.getUserInfo';
+ console.error(errorMsg);
+ response.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST;
+ response.error = errorMsg;
+ reject(response)
+ }
+
+ })
+}
+UserManagement.create = function(req) {
+ var self = this;
+ var api_server = req.query['api_server'];
+ var data = req.body;
+ data = {
+ "user":[data]
+ }
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ rp({
+ uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config',
+ method: 'POST',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ json: data,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ })
+ ]).then(function(result) {
+ var response = {};
+ response['data'] = {};
+ if (result[0].body) {
+ response['data'] = result[0].body;
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with UserManagement.create', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to create user' + error
+ };
+ reject(response);
+ });
+ });
+};
+UserManagement.update = function(req) {
+ var self = this;
+ var api_server = req.query['api_server'];
+ var bodyData = req.body;
+ data = {
+ "rw-user:user": bodyData
+ }
+ var updateTasks = [];
+ if(bodyData.hasOwnProperty('old-password')) {
+ var changePW = rp({
+ uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/operations/change-password',
+ method: 'POST',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ json: {
+ "input": {
+ 'user-name' : bodyData['user-name'],
+ 'user-domain' : bodyData['user-domain'],
+ 'old-password' : bodyData['old-password'],
+ 'new-password' : bodyData['new-password'],
+ 'confirm-password' : bodyData['confirm-password'],
+ }
+ },
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ });
+ updateTasks.push(changePW);
+ };
+ var updateUser = rp({
+ uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config/user/' + encodeURIComponent(bodyData['user-name']) + ',' + encodeURIComponent(bodyData['user-domain']),
+ method: 'PUT',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ json: data,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ });
+ updateTasks.push(updateUser)
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ updateTasks
+ ]).then(function(result) {
+ var response = {};
+ response['data'] = {};
+ if (result[0].body) {
+ response['data'] = result[0].body;
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with UserManagement.passwordChange', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to passwordChange user' + error
+ };
+ reject(response);
+ });
+ });
+};
+
+UserManagement.delete = function(req) {
+ var self = this;
+ var username = req.params.username;
+ var domain = req.params.domain;
+ var api_server = req.query["api_server"];
+ var requestHeaders = {};
+ var url = `${utils.confdPort(api_server)}/${API_VERSION}/api/config/user-config/user/${encodeURIComponent(username)},${encodeURIComponent(domain)}`
+ return new Promise(function(resolve, reject) {
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ rp({
+ url: url,
+ method: 'DELETE',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ }, function(error, response, body) {
+ if (utils.validateResponse('UserManagement.DELETE', error, response, body, resolve, reject)) {
+ return resolve({
+ statusCode: response.statusCode,
+ data: JSON.stringify(response.body)
+ });
+ };
+ });
+ })
+};
+UserManagement.getUserUiState = function(req) {
+ var self = this;
+ var api_server = req.query['api_server'];
+ var user = req.session.passport.user;
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ rp({
+ uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config/user/'+encodeURIComponent(user.username) + ',' + encodeURIComponent(user.domain),
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ })
+ ]).then(function(result) {
+ var response = {};
+ response['data'] = {};
+ if (result[0].body) {
+ response['data'] = JSON.parse(result[0].body);
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with UserManagement.getUserUiState', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to create user' + error
+ };
+ reject(response);
+ });
+ });
+};
+UserManagement.updateActiveProject = function(req) {
+ var self = this;
+ var api_server = req.query['api_server'];
+ var user = req.session.passport.user;
+ var data = {
+ "rw-user:user-config": {
+ "user":{
+ "user-name" : user.username,
+ "user-domain": user.domain,
+ "ui-state": {
+ "last-active-project" : req.params.projectId
+ }
+ }
+ }
+ }
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ rp({
+ uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config',
+ method: 'PATCH',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ json: data,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ })
+ ]).then(function(result) {
+ var response = {};
+ response['data'] = {};
+ if (result[0].body) {
+ response['data'] = result[0].body;
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with UserManagement.updateActiveProject', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to create user' + error
+ };
+ reject(response);
+ });
+ });
+};
+UserManagement.updateActiveUri = function(req) {
+ if (!req.session.passport) {
+ console.debug("passport gone before we got the save the active uri");
+ var response = {
+ statusCode: 500,
+ errorMessage: {
+ error: 'Failed to save active uri'
+ }};
+ return Promise.resolve(response);
+ }
+ var self = this;
+ var api_server = req.query['api_server'];
+ var user = req.session.passport.user;
+ var ref = req.headers.referer;
+ var hash = req.query.hash;
+ var data = {
+ "rw-user:user-config": {
+ "user":{
+ "user-name" : user.username,
+ "user-domain": user.domain,
+ "ui-state": {
+ "last-active-uri" : ref + decodeURIComponent(hash)
+ }
+ }
+ }
+ }
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ rp({
+ uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config',
+ method: 'PATCH',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ json: data,
+ rejectUnauthorized: false,
+ resolveWithFullResponse: true
+ })
+ ]).then(function(result) {
+ var response = {};
+ response['data'] = {};
+ if (result[0].body) {
+ response['data'] = result[0].body;
+ }
+ response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK
+
+ resolve(response);
+ }).catch(function(error) {
+ var response = {};
+ console.log('Problem with UserManagement.updateActiveProject', error);
+ response.statusCode = error.statusCode || 500;
+ response.errorMessage = {
+ error: 'Failed to create user' + error
+ };
+ reject(response);
+ });
+ });
+};
+module.exports = UserManagement;
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
if (!NAVIGATION[plugin_name]) {
NAVIGATION[plugin_name] = {};
}
-
if (!NAVIGATION[plugin_name].routes) {
NAVIGATION[plugin_name].routes = routes;
} else {
NAVIGATION[plugin_name].label = label || 'RW.UI Plugin';
}
+function addAllow(plugin_name, allow) {
+ if (!NAVIGATION[plugin_name]) {
+ NAVIGATION[plugin_name] = {};
+ }
+ NAVIGATION[plugin_name].allow = allow || '*';
+}
+
+function addAdminFlag(plugin_name, admin_link) {
+ if (!NAVIGATION[plugin_name]) {
+ NAVIGATION[plugin_name] = {};
+ }
+ NAVIGATION[plugin_name].admin_link = admin_link || false;
+}
+
function getNavigation() {
return NAVIGATION;
}
addOrder(plugin_name, plugin.order);
addPriority(plugin_name, plugin.priority);
addLabel(plugin_name, plugin.name);
+ addAllow(plugin_name, plugin.allow);
+ addAdminFlag(plugin_name, plugin.admin_link);
}
function init() {
--- /dev/null
+
+/*
+ *
+ * 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.
+ *
+ */
+
+/**
+ * auth routes module. Provides a RESTful API for this
+ * skyquake instance's auth state.
+ * @module framework/core/modules/routes/auth
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+var configurationAPI = require('../api/configuration');
+
+var auth = {};
+
+auth.routes = function(authManager) {
+ console.log('Configuring auth routes');
+ Router.use(bodyParser.json());
+ Router.use(cors());
+ Router.use(bodyParser.urlencoded({
+ extended: true
+ }));
+
+ // Define routes.
+ Router.get('/', function(req, res) {
+ var default_page = null;
+ var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server);
+ if (req.session && req.session.topApplication) {
+ default_page = utils.buildRedirectURL(req, configurationAPI.globalConfiguration, req.session.topApplication);
+ } else {
+ default_page = utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'user_management', '#/user-profile');
+ }
+ if (!req.user) {
+ res.redirect('/login');
+ } else {
+ res.redirect(default_page);
+ }
+ });
+
+ Router.get('/login', cors(), function(req, res) {
+ // res.render('login.html');
+ res.redirect('/login/idp');
+ });
+
+ Router.get('/login/idp',
+ authManager.passport.authenticate('oauth2')
+ );
+
+ Router.get('/callback', function(req, res, next) {
+ authManager.passport.authenticate('oauth2', function(err, user, info) {
+ if (err) {
+ // Catch some errors specific to deployments (e.g. IDP unavailable)
+ if (err.oauthError && err.oauthError.code == 'ENOTFOUND') {
+ return res.render('idpconnectfail.ejs', {
+ callback_url: req.url
+ });
+ }
+ return res.redirect('/login');
+ }
+ if (!user) {
+ return res.redirect('/login');
+ }
+ req.logIn(user, function(err) {
+ if (err) {
+ return next(err);
+ }
+ return res.redirect('/session?redirectParams=' + req.url);
+ });
+ })(req, res, next);
+ });
+
+
+ Router.get('/login.html', cors(), function(req, res) {
+ res.render('login.html');
+ });
+}
+
+auth.router = Router;
+
+module.exports = auth;
});
});
-Router.get('/check-auth', function(req, res) {
- console.log('testing auth')
- var api_server = req.query["api_server"];
- var uri = utils.confdPort(api_server) + '/api/config/';
-
- checkAuth(uri, req).then(function(data) {
- utils.sendSuccessResponse(data, res);
- }, function(error) {
- utils.sendErrorResponse(error, res);
- });
-});
-
-function checkAuth(uri, req){
- return new Promise(function(resolve, reject) {
- request({
- uri: uri,
- method: 'GET',
- headers: _.extend({}, {
- 'Authorization': req.get('Authorization'),
- forever: CONSTANTS.FOREVER_ON,
- rejectUnauthorized: false,
- })
- }, function(error, response, body) {
- console.log(arguments)
- if( response.statusCode == 401) {
- reject({statusCode: 401, error: response.body});
- } else {
- resolve({statusCode:200, data:response.body})
- }
- });
- });
-}
-
module.exports = Router;
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
var Router = require('express').Router();
var utils = require('../../api_utils/utils');
var configurationAPI = require('../api/configuration');
+var csrfCheck = require('../../api_utils/csrf').csrfCheck;
Router.use(bodyParser.json());
Router.use(cors());
extended: true
}));
-Router.get('/', cors(), function(req, res, next) {
- res.redirect('/launchpad/?api_server=' + req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server + '&upload_server=' + req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname));
+//Should have a way of adding excluded routes to this via plugin registry, instead of hard coding
+Router.use(/^(?!.*(login\/idp|session|composer\/upload|composer\/update)).*/, function(req, res, next) {
+ var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server);
+ if (req.session && req.session.loggedIn) {
+ switch (req.method) {
+ case 'POST':
+ case 'PUT':
+ csrfCheck(req, res, next);
+ break;
+ default:
+ next();
+ break;
+ }
+ } else {
+ console.log('Redirect to login.html');
+ res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'login', '&referer=' + encodeURIComponent(req.headers.referer)));
+ }
});
+
Router.get('/nav', cors(), function(req, res) {
- navAPI.get(req).then(function(data) {
- utils.sendSuccessResponse(data, res);
- }, function(error) {
- utils.sendErrorResponse(error, res);
- });
+ navAPI.get(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
});
Router.get('/nav/:plugin_id', cors(), function(req, res) {
- navAPI.get(req).then(function(data) {
- utils.sendSuccessResponse(data, res);
- }, function(error) {
- utils.sendErrorResponse(error, res);
- });
+ navAPI.get(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
});
Router.post('/nav/:plugin_id', cors(), function(req, res) {
- navAPI.create(req).then(function(data) {
- utils.sendSuccessResponse(data, res);
- }, function(error) {
- utils.sendErrorResponse(error, res);
- });
+ navAPI.create(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
});
Router.put('/nav/:plugin_id/:route_id', cors(), function(req, res) {
- navAPI.update(req).then(function(data) {
- utils.sendSuccessResponse(data, res);
- }, function(error) {
- utils.sendErrorResponse(error, res);
- });
+ navAPI.update(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
});
Router.delete('/nav/:plugin_id/:route_id', cors(), function(req, res) {
- navAPI.delete(req).then(function(data) {
- utils.sendSuccessResponse(data, res);
- }, function(error) {
- utils.sendErrorResponse(error, res);
- });
+ navAPI.delete(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
});
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * inactivity routes module. Provides a RESTful API for this
+ * skyquake instance's inactivity state.
+ * @module framework/core/modules/routes/inactivity
+ * @author Laurence Maultsby <laurence.maultsby@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+var ProjectManagementAPI = require('../api/projectManagementAPI.js');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+Router.get('/project', cors(), function(req, res) {
+ ProjectManagementAPI.get(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+Router.post('/project', cors(), function(req, res) {
+ ProjectManagementAPI.create(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+Router.put('/project', cors(), function(req, res) {
+ ProjectManagementAPI.update(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+Router.delete('/project/:projectname', cors(), function(req, res) {
+ ProjectManagementAPI.delete(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+Router.put('/platform', cors(), function(req, res) {
+ ProjectManagementAPI.updatePlatform(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+Router.get('/platform', cors(), function(req, res) {
+ ProjectManagementAPI.getPlatform(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+module.exports = Router;
+
+
+
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * Node sessions routes module.
+ * Provides a RESTful API to manage sessions.
+ * @module framework/core/modules/routes/sessions
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var sessionsAPI = require('../api/sessions');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+var CONSTANTS = require('../../api_utils/constants.js');
+var request = require('request');
+var _ = require('lodash');
+
+var sessions = {};
+
+sessions.routes = function(sessionsConfig) {
+ Router.use(bodyParser.json());
+ Router.use(cors());
+ Router.use(bodyParser.urlencoded({
+ extended: true
+ }));
+
+ // Overloaded get method to handle OpenIDConnect redirect to establish a session.
+ Router.get('/session*', cors(), /*sessionsConfig.authManager.passport.authenticate('main', {
+ noredirect: false
+ }), */function(req, res) {
+ req.query['api_server'] = sessionsConfig.api_server_protocol + '://' + sessionsConfig.api_server;
+ sessionsAPI.create(req, res).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ });
+ });
+
+ // For project switcher UI
+ Router.put('/session/:projectId', cors(), function(req, res) {
+ sessionsAPI.addProjectToSession(req, res).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+ });
+
+ Router.delete('/session', cors(), function(req, res) {
+ sessionsAPI.delete(req, res).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ });
+ });
+}
+
+sessions.router = Router;
+
+
+module.exports = sessions;
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * inactivity routes module. Provides a RESTful API for this
+ * skyquake instance's inactivity state.
+ * @module framework/core/modules/routes/inactivity
+ * @author Laurence Maultsby <laurence.maultsby@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+var UserManagementAPI = require('../api/userManagementAPI.js');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+Router.get('/user', cors(), function(req, res) {
+ UserManagementAPI.get(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+Router.get('/user-profile', cors(), function(req, res) {
+ UserManagementAPI.getProfile(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+Router.get('/user-data/:userId/:domain?', cors(), function(req, res) {
+ UserManagementAPI.getUserInfo(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+Router.post('/user', cors(), function(req, res) {
+ UserManagementAPI.create(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+Router.put('/user', cors(), function(req, res) {
+ UserManagementAPI.update(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+Router.delete('/user/:username/:domain', cors(), function(req, res) {
+ UserManagementAPI.delete(req).then(function(response) {
+ utils.sendSuccessResponse(response, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+module.exports = Router;
+
+
+
--- /dev/null
+<% if (!user) { %>
+ <p>Welcome! Please <a href="/login">log in</a>.</p>
+<% } else { %>
+ <p>Hello, <%= user.username %>. View your <a href="<%= default_page %>">default page</a>. TODO: Update link to dashboard</p>
+<% } %>
\ No newline at end of file
--- /dev/null
+<style type="text/css">
+
+html {
+ background: #f1f1f1;
+}
+
+body {
+ background: #f1f1f1;
+}
+#idpfail {
+ display: flex;
+ flex-direction: column;
+ text-align: center;
+ align-items: center;
+}
+#idpfail .message {
+ display: flex;
+ margin-bottom: 20px;
+ font-size: 1.625rem;
+ font-weight: 400;
+ text-decoration: none;
+ text-transform: uppercase;
+ font-family: roboto-thin, Helvetica, Arial, sans-serif;
+ color: #CD5C5C;
+}
+#retrylink {
+ margin-top:40px;
+ font-family: roboto-thin, Helvetica, Arial, sans-serif;
+}
+
+
+</style>
+<div id="idpfail">
+ <p class="message">
+ We are having trouble connecting to the Identity Provider.
+ </p>
+ <p class="message">
+ Please check that it is running and reachable.
+ </p>
+ <p id="retrylink">
+ Once you have resolved the connectivity issues, <a href="<%= callback_url %>">please click here to retry.</a>
+ </p>
+</div>
\ No newline at end of file
--- /dev/null
+<style>
+ html {
+ display: none;
+ }
+</style>
+<script>
+ if (self == top) {
+ document.documentElement.style.display = 'block';
+ } else {
+ top.location = self.location;
+ }
+</script>
+<div id="app"></div>
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import appConfiguration from '../utils/appConfiguration'
+
+let versionKeyPrefix = null;
+
+const localCache = new class {
+ get(key) {
+ let valueAsString = localStorage.getItem(key);
+ return valueAsString ? JSON.parse(valueAsString) : undefined;
+ }
+ set(key, val) {
+ localStorage.setItem(key, typeof val === 'string' ? val : JSON.stringify(val));
+ }
+}();
+
+let objCache = new Map();
+
+const storeCache = new class {
+ get(key) {
+ if (objCache[key]) {
+ objCache[key].timerId && clearTimeout(objCache[key].timerId)
+ objCache[key].timerId = setTimeout((key) => delete objCache[key], 2000)
+ return objCache[key].value;
+ }
+ const obj = localCache.get(key);
+ if (obj) {
+ objCache[key] = {
+ value: obj,
+ timerId: setTimeout((key) => delete objCache[key], 2000)
+ }
+ return obj;
+ }
+ }
+ set(key, obj) {
+ setTimeout(localCache.set, 100, key, obj);
+ objCache[key] = {
+ value: obj,
+ timerId: setTimeout((key) => delete objCache[key], 2000)
+ }
+ }
+ init(version) {
+ versionKeyPrefix = 's-v-' + version;
+ const currentStoreVersion = localStorage.getItem('store-version');
+ if (currentStoreVersion !== version) {
+ let removeItems = [];
+ for (let i = 0; i < localStorage.length; ++i) {
+ let key = localStorage.key(i);
+ if (key.startsWith('s-v-')) {
+ removeItems.push(key);
+ }
+ }
+ removeItems.forEach((key) => localStorage.removeItem(key));
+ localStorage.setItem('store-version', version);
+ }
+ }
+}();
+
+class StoreCache {
+ constructor(name) {
+ this.name = 's-v-' + name;
+ }
+ get(key) {
+ return storeCache.get(this.name + key);
+ }
+ set(key, obj) {
+ storeCache.set(this.name + key, obj);
+ }
+ init() {
+ return versionKeyPrefix ? Promise.resolve() : new Promise(
+ (resolve, reject) => {
+ appConfiguration.get().then((config) => {
+ storeCache.init(config.version);
+ resolve();
+ })
+ }
+ )
+ }
+}
+
+module.exports = StoreCache;
\ No newline at end of file
--- /dev/null
+import modelActions from './modelActions'
+import modelSource from './modelSource'
+
+export {
+ modelSource,
+ modelActions
+}
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import Alt from 'widgets/skyquake_container/skyquakeAltInstance';
+
+class Actions {
+
+ constructor() {
+ this.generateActions(
+ 'loadModel',
+ 'processRequestSuccess',
+ 'processRequestInitiated',
+ 'processRequestFailure');
+ }
+}
+
+export default Alt.createActions(Actions);
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import rw from 'utils/rw'
+import modelActions from './modelActions'
+import Utils from 'utils/utils'
+import $ from 'jquery';
+
+const source = {
+ loadModel: function () {
+ return {
+ remote: function (state, path) {
+ return new Promise(function (resolve, reject) {
+ $.ajax({
+ url: '/model?path=' + path,
+ type: 'GET',
+ success: function (result) {
+ resolve(result);
+ },
+ error: function (xhr, errorType, errorMessage) {
+ console.log("There was an error updating the model: ", errorType, errorMessage, xhr);
+ reject({response: xhr.responseJSON, errorType, errorMessage});
+ }
+ });
+ })
+ },
+ success: modelActions.processRequestSuccess,
+ loading: modelActions.processRequestInitiated,
+ error: modelActions.processRequestFailure
+ }
+ },
+ updateModel: function () {
+ return {
+ remote: function (state, path, data) {
+ const url = path.reduce((url, node) => {
+ url += node[0] !== '[' ? '/' : '';
+ return url + node
+ }, `/model?path=/${state.path}`);
+ return new Promise(function (resolve, reject) {
+ $.ajax({
+ url: url,
+ type: 'PATCH',
+ data: data,
+ success: function (result) {
+ resolve(result);
+ },
+ error: function (xhr, errorType, errorMessage) {
+ console.log("There was an error updating the model: ", errorType, errorMessage, xhr);
+ reject({response: xhr.responseJSON, errorType, errorMessage});
+ }
+ });
+ })
+ },
+ success: modelActions.processRequestSuccess,
+ loading: modelActions.processRequestInitiated,
+ error: modelActions.processRequestFailure
+ }
+ },
+ createModel: function () {
+ return {
+ remote: function (state, path, data) {
+ const url = path.reduce((url, node) => {
+ url += node[0] !== '[' ? '/' : '';
+ return url + node
+ }, `/model?path=/${state.path}`);
+ return new Promise(function (resolve, reject) {
+ $.ajax({
+ url: url,
+ type: 'PUT',
+ data: data,
+ success: function (result) {
+ resolve(result);
+ },
+ error: function (xhr, errorType, errorMessage) {
+ console.log("There was an error updating the model: ", errorType, errorMessage, xhr);
+ reject({response: xhr.responseJSON, errorType, errorMessage});
+ }
+ });
+ })
+ },
+ success: modelActions.processRequestSuccess,
+ loading: modelActions.processRequestInitiated,
+ error: modelActions.processRequestFailure
+ }
+ },
+
+ deleteModel: function () {
+ return {
+ remote: function (state, path) {
+ const url = path.reduce((url, node) => {
+ url += node[0] !== '[' ? '/' : '';
+ return url + node
+ }, `/model?path=/${state.path}`);
+ return new Promise(function (resolve, reject) {
+ $.ajax({
+ url: url,
+ type: 'DELETE',
+ success: function (result) {
+ resolve(result);
+ },
+ error: function (xhr, errorType, errorMessage) {
+ console.log("There was an error updating the model: ", errorType, errorMessage, xhr);
+ reject({response: xhr.responseJSON, errorType, errorMessage});
+ }
+ });
+ })
+ },
+ success: modelActions.processRequestSuccess,
+ loading: modelActions.processRequestInitiated,
+ error: modelActions.processRequestFailure
+ }
+ }
+}
+module.exports = source;
\ No newline at end of file
--- /dev/null
+import schemaActions from './schemaActions'
+import schemaSource from './schemaSource'
+
+export {
+ schemaSource,
+ schemaActions
+}
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import Alt from 'widgets/skyquake_container/skyquakeAltInstance';
+
+class Actions {
+
+ constructor() {
+ this.generateActions(
+ 'loadSchema',
+ 'loadSchemaSuccess',
+ 'loadSchemaLoading',
+ 'loadSchemaFail');
+ }
+}
+
+export default Alt.createActions(Actions);
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import rw from 'utils/rw'
+import schemaActions from './schemaActions'
+import Utils from 'utils/utils'
+import $ from 'jquery';
+import StoreCache from '../SourceCache'
+
+const storeCache = new StoreCache('schema');
+storeCache.init(); // get the ball rolling
+
+function getCachedSchema(request) {
+ const cachedSchema = {};
+ const requestSchema = [];
+ request.forEach((path) => {
+ let schema = storeCache.get(path);
+ if (schema) {
+ cachedSchema[path] = schema
+ } else {
+ requestSchema.push(path);
+ }
+ });
+ return {
+ cachedSchema,
+ requestSchema
+ };
+}
+
+const schemaSource = {
+ loadSchema: function () {
+ return {
+ local: function (state, request) {
+ request = Array.isArray(request) ? request : [request];
+ const results = getCachedSchema(request);
+ if (!results.requestSchema.length) {
+ return(Promise.resolve(results.cachedSchema));
+ }
+ },
+
+ remote: function (state, request) {
+ return new Promise(function (resolve, reject) {
+ storeCache.init().then(() => {
+ request = Array.isArray(request) ? request : [request];
+ const results = getCachedSchema(request);
+ if (!results.requestSchema.length) {
+ resolve({
+ schema: results.cachedSchema
+ });
+ } else {
+ $.ajax({
+ url: '/schema?request=' + results.requestSchema.join(','),
+ type: 'GET',
+ success: function ({
+ schema
+ }) {
+ for (let path in schema) {
+ storeCache.set(path, schema[path]);
+ }
+ resolve(getCachedSchema(request).cachedSchema);
+ },
+ error: function (error) {
+ console.log("There was an error getting the schema: ", error);
+ reject(error);
+ }
+ }).fail(function (xhr) {
+ console.log("There was an error getting the schema: ", xhr);
+ Utils.checkAuthentication(xhr.status);
+ reject("There was an error getting the schema.")
+ });
+ }
+ })
+ })
+ },
+ success: schemaActions.loadSchemaSuccess,
+ loading: schemaActions.loadSchemaLoading,
+ error: schemaActions.loadSchemaFail
+ }
+ },
+}
+export default schemaSource;
\ No newline at end of file
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
$error-red:#FF5F5F;
-//PC
+/*PC*/
$black: #000;
$gray-lightest: #f1f1f1;
$gray-darker: #666;
$gray-darkest: #333;
$white: #FFF;
-//
-// Brand Colors
-//
+/**/
+/* Brand Colors*/
+/**/
$brand-blue-light: #30baef;
$brand-blue: #00acee;
$brand-blue-dark: #147ca3;
$neutral-light-4: hsl(360, 100%, 50%);
$neutral-light-5: hsl(360, 100%, 50%);
-$neutral-dark-1: hsl(360, 100%, 50%);
-$neutral-dark-2: hsl(360, 100%, 50%);
-$neutral-dark-3: hsl(360, 100%, 50%);
-$neutral-dark-4: hsl(360, 100%, 50%);
-$neutral-dark-5: hsl(360, 100%, 50%);
+$neutral-dark-0: hsl(0, 0%, 80.7%);
+$neutral-dark-1: hsl(0, 0%, 63.7%);
+$neutral-dark-2: hsl(0, 0%, 56.7%);
+$neutral-dark-3: hsl(0, 0%, 49.7%);
+$neutral-dark-4: hsl(0, 0%, 42.7%);
+$neutral-dark-5: hsl(0, 0%, 35.7%);
$netral-black: hsl(0, 100%, 0%);
*
*/
/*@import "./vendor/css-reset-2.0/css-reset.css";*/
-@import "../../node_modules/reset-css/reset.css";
-
.has-drop-shadow {
box-shadow: 2px 2px rgba(0, 0, 0, .15)
}
margin-top: 150px;
margin-bottom: 20px;
background-size: 154px 102px;
- /*background-image: url(./img/header-logo.png)*/
+ /*background-image: url(./img/header-logo.png)*/
background-image: url(./img/svg/osm-logo_color_rgb.svg);
}
.login-cntnr .riftio {
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import $ from 'jquery';
+
+const configPromise = new Promise((resolve, reject) => {
+ $.ajax({
+ url: '/app-config',
+ type: 'GET',
+ success: function (data) {
+ if (data.version.endsWith('.1')){
+ data.version = '' + Date.now();
+ }
+ resolve(data);
+ },
+ error: function (error) {
+ console.log("There was an error getting config: ", error);
+ reject(error);
+ }
+ }).fail(function (xhr) {
+ console.log('There was an xhr error getting the config', xhr);
+ reject(xhr);
+ });
+});
+
+module.exports = {
+ get: () => configPromise
+};
\ No newline at end of file
--- /dev/null
+var c = {};
+
+c.PLATFORM = {
+ OPER: "rw-rbac-platform:platform-oper",
+ ADMIN: "rw-rbac-platform:platform-admin",
+ SUPER: "rw-rbac-platform:super-admin"
+}
+
+c.PROJECT = {
+ TYPE: {
+ "rw-project-mano:catalog-oper": "rw-project-mano",
+ "rw-project-mano:catalog-admin": "rw-project-mano",
+ "rw-project-mano:lcm-oper": "rw-project-mano",
+ "rw-project-mano:lcm-admin": "rw-project-mano",
+ "rw-project-mano:account-oper": "rw-project-mano",
+ "rw-project-mano:account-admin": "rw-project-mano",
+ "rw-project:project-oper": "rw-project",
+ "rw-project:project-admin": "rw-project"
+ },
+ CATALOG_OPER: "rw-project-mano:catalog-oper",
+ CATALOG_ADMIN: "rw-project-mano:catalog-admin",
+ LCM_OPER: "rw-project-mano:lcm-oper",
+ LCM_ADMIN: "rw-project-mano:lcm-admin",
+ ACCOUNT_OPER: "rw-project-mano:account-oper",
+ ACCOUNT_ADMIN: "rw-project-mano:account-admin",
+ PROJECT_OPER: "rw-project:project-oper",
+ PROJECT_ADMIN: "rw-project:project-admin"
+
+}
+
+module.exports = c;
* limitations under the License.
*
*/
-//Login needs to be refactored. Too many cross dependencies
-var AuthActions = require('../widgets/login/loginAuthActions.js');
-var $ = require('jquery');
-import rw from './rw.js';
+import AuthActions from '../widgets/login/loginAuthActions';
+import $ from 'jquery';
+import rw from './rw';
+import appConfiguration from './appConfiguration'
+import SockJS from 'sockjs-client';
+
var API_SERVER = rw.getSearchParams(window.location).api_server;
let NODE_PORT = rw.getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
-var SockJS = require('sockjs-client');
var Utils = {};
var INACTIVITY_TIMEOUT = 600000;
-Utils.getInactivityTimeout = function() {
- return new Promise(function(resolve, reject) {
- $.ajax({
- url: '/inactivity-timeout',
- type: 'GET',
- success: function(data) {
- resolve(data);
- },
- error: function(error) {
- console.log("There was an error getting the inactivity-timeout: ", error);
- reject(error);
- }
- }).fail(function(xhr) {
- console.log('There was an xhr error getting the inactivity-timeout', xhr);
- reject(xhr);
- });
- });
-};
-
Utils.isMultiplexerLoaded = function() {
if (window.multiplexer) {
return true;
loadChecker();
};
-Utils.checkAndResolveSocketRequest = function(data, resolve, reject) {
+Utils.checkAndResolveSocketRequest = function(data, resolve, reject, successCallback) {
const checker = () => {
if (!Utils.isMultiplexerLoaded()) {
setTimeout(() => {
checker();
}, 500);
} else {
- resolve(data.id);
+ if (!successCallback) {
+ resolve(data.id);
+ } else {
+ //resolve handled in callback
+ successCallback(data.id)
+ }
}
};
};
Utils.bootstrapApplication = function() {
- var self = this;
- return new Promise(function(resolve, reject) {
- Promise.all([self.getInactivityTimeout()]).then(function(results) {
+ return new Promise((resolve, reject) => {
+ Promise.all([appConfiguration.get()]).then(function(results) {
INACTIVITY_TIMEOUT = results[0]['inactivity-timeout'];
resolve();
}, function(error) {
}
Utils.addAuthorizationStub = function(xhr) {
- var Auth = window.sessionStorage.getItem("auth");
- xhr.setRequestHeader('Authorization', 'Basic ' + Auth);
+ // NO-OP now that we are dealing with it on the server
+ // var Auth = window.sessionStorage.getItem("auth");
+ // xhr.setRequestHeader('Authorization', 'Basic ' + Auth);
};
Utils.getByteDataWithUnitPrefix = function(number, precision) {
}
}
Utils.loginHash = "#/login";
-Utils.setAuthentication = function(username, password, cb) {
- var self = this;
- var AuthBase64 = btoa(username + ":" + password);
- window.sessionStorage.setItem("auth", AuthBase64);
- self.detectInactivity();
- $.ajax({
- url: '//' + window.location.hostname + ':' + window.location.port + '/check-auth?api_server=' + API_SERVER,
- type: 'GET',
- beforeSend: Utils.addAuthorizationStub,
- success: function(data) {
- //console.log("LoggingSource.getLoggingConfig success call. data=", data);
- if (cb) {
- cb();
- };
- },
- error: function(data) {
- Utils.clearAuthentication();
- }
- });
-}
-Utils.clearAuthentication = function(callback) {
+
+Utils.clearAuthentication = function() {
var self = this;
window.sessionStorage.removeItem("auth");
AuthActions.notAuthenticated();
window.sessionStorage.setItem("locationRefHash", window.location.hash);
- if (callback) {
- callback();
- } else {
- window.location.hash = Utils.loginHash;
- }
+ var reloadURL = '';
+ $.ajax({
+ url: '//' + window.location.hostname + ':' + window.location.port + '/session?api_server=' + API_SERVER + '&hash=' + encodeURIComponent(window.location.hash),
+ type: 'DELETE',
+ success: function(data) {
+ console.log('User logged out');
+ reloadURL = data['url'] + '?post_logout_redirect_uri=' +
+ window.location.protocol + '//' +
+ window.location.hostname + ':' +
+ window.location.port +
+ '/?api_server=' + API_SERVER;
+
+ window.location.replace(reloadURL);
+ },
+ error: function(data) {
+ console.log('Problem logging user out');
+ }
+ });
}
Utils.isNotAuthenticated = function(windowLocation, callback) {
var self = this;
return displayMsg
}
+Utils.rpcError = (rpcResult) => {
+ try {
+ let info = JSON.parse(rpcResult);
+ let rpcError = info.body || info.errorMessage.body || info.errorMessage.error;
+ if (rpcError) {
+ if (typeof rpcError === 'string') {
+ const index = rpcError.indexOf('{');
+ if (index >= 0) {
+ return JSON.parse(rpcError.substr(index));
+ }
+ } else {
+ return rpcError;
+ }
+ }
+ } catch (e) {
+ }
+ console.log('invalid rpc error: ', rpcResult);
+ return null;
+}
+
module.exports = Utils;
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
############################################################################ */
.SqButton {
- align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
border-style: solid;
border-radius: 3px;
border-width: 0px;
cursor: pointer;
+ display: -ms-inline-flexbox;
display: inline-flex;
font-size: 1rem;
height: 50px;
- justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
margin: 0 10px;
outline: none;
padding: 0 15px;
/* Focus */
&:focus {
- // box-shadow: $focus-shadow;
- border: 1px solid red;
+ /* box-shadow: $focus-shadow;*/
+ border: 1px solid;
+ border-color: darken($normalHoverBackground, 10%);
}
/* SIZES
fill: $primaryForeground;
}
}
-
-
}
-
+.sqButtonGroup {
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex-pack: center;
+ justify-content: center;
+}
Class += " is-disabled";
}
return (
- <div style={{display: 'flex'}}>
+ <div style={{display: 'flex'}} onClick={this.props.onClick}>
<div className={Class} tabIndex="0">
- {svgHTML}
- <div className="SqButton-content">{label}</div>
+ {svgHTML}
+ <div className="SqButton-content">{label}</div>
</div>
+ </div>
+ )
+ }
+}
+
+export class ButtonGroup extends React.Component {
+ render() {
+ let className = "sqButtonGroup";
+ if (this.props.className) {
+ className = `${className} ${this.props.className}`
+ }
+ return (
+ <div className={className} style={this.props.style}>
+ {this.props.children}
</div>
)
}
}
+
SqButton.defaultProps = {
+ onClick: function(e) {
+ console.log('Clicked')
+ },
icon: false,
primary: false,
disabled: false,
// Histogram: Histogram,
Multicomponent: require('./multicomponent/multicomponent.js'),
Mixins: require('./mixins/ButtonEventListener.js'),
- // Gauge: require('./gauge/gauge.js'),
+// Gauge: require('./gauge/gauge.js'),
Bullet: require('./bullet/bullet.js')
};
-// require('../../assets/js/n3-line-chart.js');
-// var Gauge = require('../../assets/js/gauge-modified.js');
-// var bulletController = function($scope, $element) {
-// this.$element = $element;
-// this.vertical = false;
-// this.value = 0;
-// this.min = 0;
-// this.max = 100;
-// //this.range = this.max - this.min;
-// //this.percent = (this.value - this.min) / this.range;
-// this.displayValue = this.value;
-// this.isPercent = (this.units == '')? true:false;
-// this.bulletColor = "#6BB814";
-// this.fontsize = 28;
-// this.radius = 4;
-// this.containerMarginX = 0;
-// this.containerMarginY = 0;
-// this.textMarginX = 5;
-// this.textMarginY = 42;
-// this.bulletMargin = 0;
-// this.width = 512;
-// this.height = 64;
-// this.markerX = -100; // puts it off screen unless set
-// var self = this;
-// if (this.isPercent) {
-// this.displayValue + "%";
-// }
-// $scope.$watch(
-// function() {
-// return self.value;
-// },
-// function() {
-// self.valueChanged();
-// }
-// );
-
-// }
-
-// bulletController.prototype = {
-
-// valueChanged: function() {
-// var range = this.max - this.min;
-// var normalizedValue = (this.value - this.min) / range;
-// if (this.isPercent) {
-// this.displayValue = String(Math.round(normalizedValue * 100)) + "%";
-// } else {
-// this.displayValue = this.value;
-// }
-// // All versions of IE as of Jan 2015 does not support inline CSS transforms on SVG
-// if (platform.name == 'IE') {
-// this.bulletWidth = Math.round(100 * normalizedValue) + '%';
-// } else {
-// this.bulletWidth = this.width - (2 * this.containerMarginX);
-// var transform = 'scaleX(' + normalizedValue + ')';
-// var bullet = $(this.$element).find('.bullet2');
-// bullet.css('transform', transform);
-// bullet.css('-webkit-transform', transform);
-// }
-// },
-
-// markerChanged: function() {
-// var range = this.max - this.min;
-// var w = this.width - (2 * this.containerMarginX);
-// this.markerX = this.containerMarginX + ((this.marker - this.min) / range ) * w;
-// this.markerY1 = 7;
-// this.markerY2 = this.width - 7;
-// }
-// }
-
-// angular.module('components', ['n3-line-chart'])
-// .directive('rwBullet', function() {
-// return {
-// restrict : 'E',
-// templateUrl: 'modules/views/rw.bullet.tmpl.html',
-// bindToController: true,
-// controllerAs: 'bullet',
-// controller: bulletController,
-// replace: true,
-// scope: {
-// min : '@?',
-// max : '@?',
-// value : '@',
-// marker: '@?',
-// units: '@?',
-// bulletColor: '@?',
-// label: '@?'
-// }
-// };
-// })
-// .directive('rwSlider', function() {
-// var controller = function($scope, $element, $timeout) {
-// // Q: is there a way to force attributes to be ints?
-// $scope.min = $scope.min || "0";
-// $scope.max = $scope.max || "100";
-// $scope.step = $scope.step || "1";
-// $scope.height = $scope.height || "30";
-// $scope.orientation = $scope.orientation || 'horizontal';
-// $scope.tooltipInvert = $scope.tooltipInvert || false;
-// $scope.percent = $scope.percent || false;
-// $scope.kvalue = $scope.kvalue || false;
-// $scope.direction = $scope.direction || "ltr";
-// $($element).noUiSlider({
-// start: parseInt($scope.value),
-// step: parseInt($scope.step),
-// orientation: $scope.orientation,
-// range: {
-// min: parseInt($scope.min),
-// max: parseInt($scope.max)
-// },
-// direction: $scope.direction
-// });
-// //$(".no-Ui-target").Link('upper').to('-inline-<div class="tooltip"></div>')
-// var onSlide = function(e, value) {
-// $timeout(function(){
-// $scope.value = value;
-// })
-
-// };
-// $($element).on({
-// change: onSlide,
-// slide: onSlide,
-// set: $scope.onSet({value: $scope.value})
-// });
-// var val = String(Math.round($scope.value));
-// if ($scope.percent) {
-// val += "%"
-// } else if ($scope.kvalue) {
-// val += "k"
-// }
-// $($element).height($scope.height);
-// if ($scope.tooltipInvert) {
-// $($element).find('.noUi-handle').append("<div class='tooltip' style='position:relative;right:20px'>" + val + "</div>");
-// } else {
-// $($element).find('.noUi-handle').append("<div class='tooltip' style='position:relative;left:-20px'>" + val + "</div>");
-// }
-// $scope.$watch('value', function(value) {
-// var val = String(Math.round($scope.value));
-// if ($scope.percent) {
-// val += "%"
-// } else if($scope.kvalue) {
-// val += "k"
-// }
-// $($element).val(value);
-// $($element).find('.tooltip').html(val);
-// if ($scope.tooltipInvert) {
-// $($element).find('.tooltip').css('right', $($element).find('.tooltip').innerWidth() * -1);
-// } else {
-// $($element).find('.tooltip').css('left', $($element).find('.tooltip').innerWidth() * -1);
-// }
-// });
-// };
-
-// return {
-// restrict : 'E',
-// template: '<div></div>',
-// controller : controller,
-// replace: true,
-// scope: {
-// min : '@',
-// max : '@',
-// width: '@',
-// height: '@',
-// step : '@',
-// orientation : '@',
-// tooltipInvert: '@',
-// percent: '@',
-// kvalue: '@?',
-// onSet:'&?',
-// direction: '@?',
-// value:'=?'
-// }
-// };
-// })
-// .directive('rwGauge', function() {
-// return {
-// restrict: 'AE',
-// template: '<canvas class="rwgauge" style="width:100%;height:100%;max-width:{{width}}px;max-height:240px;"></canvas>',
-// replace: true,
-// scope: {
-// min: '@?',
-// max: '@?',
-// size: '@?',
-// color: '@?',
-// value: '@?',
-// resize: '@?',
-// isAggregate: '@?',
-// units: '@?',
-// valueFormat: '=?',
-// width: '@?'
-// },
-// bindToController: true,
-// controllerAs: 'gauge',
-// controller: function($scope, $element) {
-// var self = this;
-// this.gauge = null;
-// this.min = this.min || 0;
-// this.max = this.max || 100;
-// this.nSteps = 14;
-// this.size = this.size || 300;
-// this.units = this.units || '';
-// $scope.width = this.width || 240;
-// this.color = this.color || 'hsla(212, 57%, 50%, 1)';
-// if (!this.valueFormat) {
-// if (this.max > 1000 || this.value) {
-// self.valueFormat = {
-// "int": 1,
-// "dec": 0
-// };
-// } else {
-// self.valueFormat = {
-// "int": 1,
-// "dec": 2
-// };
-// }
-// }
-// this.isAggregate = this.isAggregate || false;
-// this.resize = this.resize || false;
-// if (this.format == 'percent') {
-// self.valueFormat = {
-// "int": 3,
-// "dec": 0
-// };
-// }
-// $scope.$watch(function() {
-// return self.max;
-// }, function(n, o) {
-// if(n !== o) {
-// renderGauge();
-// }
-// });
-// $scope.$watch(function() {
-// return self.valueFormat;
-// }, function(n, o) {
-// if(n != 0) {
-// renderGauge();
-// }
-// });
-// $scope.$watch(function() {
-// return self.value;
-// }, function() {
-// if (self.gauge) {
-// // w/o rounding gauge will unexplainably thrash round.
-// self.valueFormat = determineValueFormat(self.value);
-// self.gauge.setValue(Math.ceil(self.value * 100) / 100);
-// //self.gauge.setValue(Math.round(self.value));
-// }
-// });
-// angular.element($element).ready(function() {
-// console.log('rendering')
-// renderGauge();
-// })
-// window.testme = renderGauge;
-// function determineValueFormat(value) {
-
-// if (value > 999 || self.units == "%") {
-// return {
-// "int": 1,
-// "dec": 0
-// }
-// }
-
-// return {
-// "int": 1,
-// "dec": 2
-// }
-// }
-// function renderGauge(calcWidth) {
-// if (self.max == self.min) {
-// self.max = 14;
-// }
-// var range = self.max - self.min;
-// var step = Math.round(range / self.nSteps);
-// var majorTicks = [];
-// for (var i = 0; i <= self.nSteps; i++) {
-// majorTicks.push(self.min + (i * step));
-// };
-// var redLine = self.min + (range * 0.9);
-// var config = {
-// isAggregate: self.isAggregate,
-// renderTo: angular.element($element)[0],
-// width: calcWidth || self.size,
-// height: calcWidth || self.size,
-// glow: false,
-// units: self.units,
-// title: false,
-// minValue: self.min,
-// maxValue: self.max,
-// majorTicks: majorTicks,
-// valueFormat: determineValueFormat(self.value),
-// minorTicks: 0,
-// strokeTicks: false,
-// highlights: [],
-// colors: {
-// plate: 'rgba(0,0,0,0)',
-// majorTicks: 'rgba(15, 123, 182, .84)',
-// minorTicks: '#ccc',
-// title: 'rgba(50,50,50,100)',
-// units: 'rgba(50,50,50,100)',
-// numbers: '#fff',
-// needle: {
-// start: 'rgba(255, 255, 255, 1)',
-// end: 'rgba(255, 255, 255, 1)'
-// }
-// }
-// };
-// var min = config.minValue;
-// var max = config.maxValue;
-// var N = 1000;
-// var increment = (max - min) / N;
-// for (i = 0; i < N; i++) {
-// var temp_color = 'rgb(0, 172, 238)';
-// if (i > 0.5714 * N && i <= 0.6428 * N) {
-// temp_color = 'rgb(0,157,217)';
-// } else if (i >= 0.6428 * N && i < 0.7142 * N) {
-// temp_color = 'rgb(0,142,196)';
-// } else if (i >= 0.7142 * N && i < 0.7857 * N) {
-// temp_color = 'rgb(0,126,175)';
-// } else if (i >= 0.7857 * N && i < 0.8571 * N) {
-// temp_color = 'rgb(0,122,154)';
-// } else if (i >= 0.8571 * N && i < 0.9285 * N) {
-// temp_color = 'rgb(0,96,133)';
-// } else if (i >= 0.9285 * N) {
-// temp_color = 'rgb(0,80,112)';
-// }
-// config.highlights.push({
-// from: i * increment,
-// to: increment * (i + 2),
-// color: temp_color
-// })
-// }
-// var updateSize = _.debounce(function() {
-// config.maxValue = self.max;
-// var clientWidth = self.parentNode.parentNode.clientWidth / 2;
-// var calcWidth = (300 > clientWidth) ? clientWidth : 300;
-// self.gauge.config.width = self.gauge.config.height = calcWidth;
-// self.renderGauge(calcWidth);
-// }, 500);
-// if (self.resize) $(window).resize(updateSize)
-// if (self.gauge) {
-// self.gauge.updateConfig(config);
-// } else {
-// self.gauge = new Gauge(config);
-// self.gauge.draw();
-// }
-// };
-// },
-// }
-// });
--- /dev/null
+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}`)
+ }
+}
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* limitations under the License.
*
*/
-@import 'style/_colors.scss';
+@import '../../style/_colors.scss';
.sqTextInput {
display: -ms-flexbox;
color:$darker-gray;
text-transform:uppercase;
}
- input, .readonly, textarea {
+ input, textarea {
height: 35px;
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff;
+ /* box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff;*/
font-size: 1rem;
display: block;
background: white !important;
.readonly {
line-height: 35px;
box-shadow:none;
+ background:none !important;
}
textarea {
-ms-flex-align: stretch;
border:0px;
height: 100%;
}
+ &.checkbox {
+ -ms-flex-direction:row;
+ flex-direction:row;
+ -ms-flex-align:center;
+ align-items:center;
+ margin-bottom:0;
+ >span {
+ -ms-flex-order: 1;
+ order: 1;
+ padding-left:1rem;
+ }
+ >input {
+ -ms-flex-order: 0;
+ order: 0;
+
+ box-shadow:none;
+ height:25px;
+ }
+ }
+ .invalid {
+ color: red;
+ font-weight:strong;
+ }
+ input:invalid {
+ border: 2px solid red;
+ &:after {
+ content: 'Invalid Value'
+ }
+ }
+}
+
+.sqCheckBox {
+ display:-ms-flexbox;
+ display:flex;
+ label {
+ display:-ms-flexbox;
+ display:flex;
+ -ms-flex-align: center;
+ align-items: center;
+ input {
+ box-shadow: none;
+ height: auto;
+ margin: 0 0.25rem;
+ }
+ }
+}
+
+.FormSection {
+ &-title {
+ color: #000;
+ background: lightgray;
+ padding: 0.5rem;
+ border-top: 1px solid #f1f1f1;
+ border-bottom: 1px solid #f1f1f1;
+ }
+ &-body {
+ padding: 0.5rem 0.75rem;
+ }
+ label {
+ -ms-flex: 1 0;
+ flex: 1 0;
+ }
+ /* label {*/
+ /* display: -ms-flexbox;*/
+ /* display: flex;*/
+ /* -ms-flex-direction: column;*/
+ /* flex-direction: column;*/
+ /* width: 100%;*/
+ /* margin: 0.5rem 0;*/
+ /* -ms-flex-align: start;*/
+ /* align-items: flex-start;*/
+ /* -ms-flex-pack: start;*/
+ /* justify-content: flex-start;*/
+ /* }*/
+ select {
+ font-size: 1rem;
+ min-width: 75%;
+ height: 35px;
+ }
+}
+
+
+
+
+.InputCollection {
+ display:-ms-flexbox;
+ display:flex;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -ms-flex-align: center;
+ align-items: center;
+ button {
+ padding: 0.25rem;
+ height: 1.5rem;
+ font-size: 0.75rem;
+ }
+ select {
+ min-width: 100%;
+ }
+ margin-bottom:0.5rem;
+ &-wrapper {
+
+ }
}
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import './formControls.scss';
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import CheckSVG from '../../../node_modules/open-iconic/svg/check.svg'
+import React, {Component} from 'react';
+
+export default class Input extends Component {
+ render() {
+ let {label, value, defaultValue, ...props} = this.props;
+ let inputProperties = {
+ value: value
+ }
+ let isRequired;
+ let inputType;
+ let tester = null;
+ let className = `sqTextInput ${props.className}`;
+
+ if(this.props.required) {
+ isRequired = <span className="required">*</span>
+ }
+ if (defaultValue) {
+ inputProperties.defaultValue = defaultValue;
+ }
+ if (props.pattern) {
+ inputProperties.pattern = props.pattern;
+ tester = new RegExp(props.pattern);
+ }
+ if(props.hasOwnProperty('type') && (props.type.toLowerCase() == 'checkbox')) {
+ inputProperties.checked = props.checked;
+ className = `${className} checkbox`;
+ }
+ if (value == undefined) {
+ value = defaultValue;
+ }
+ switch(props.type) {
+ case 'textarea':
+ inputType = <textarea key={props.key} {...inputProperties} value={value} onChange={props.onChange} />
+ break;
+ case 'select':
+ inputType = <SelectOption
+ key={props.key}
+ initial={props.initial}
+ defaultValue={defaultValue}
+ options={props.options}
+ onChange={props.onChange}
+ />
+ break;
+ case 'radiogroup':
+ inputType = buildRadioButtons(this.props);
+ break;
+ default:
+ inputType = <input key={props.key} type={props.type} {...inputProperties} onChange={props.onChange} placeholder={props.placeholder}/>;
+ }
+ let displayedValue;
+ if(value === null) {
+ displayedValue = null;
+ } else {
+ displayedValue = value.toString();
+ }
+ if( props.readonly && props.type == "checkbox" && props.checked ) {
+ displayedValue = <img src={CheckSVG} />
+ }
+
+ if( props.readonly && props.type == "radiogroup" && props.readonlydisplay ) {
+ displayedValue = props.readonlydisplay
+ }
+
+ let html = (
+ <label className={className} style={props.style}>
+ <span> { label } {isRequired}</span>
+ {
+ !props.readonly ? inputType : <div className="readonly">{displayedValue}</div>
+ }
+ {
+ !props.readonly && tester && value && !tester.test(value) ? <span className="invalid">The Value you entered is invalid</span> : null
+ }
+ </label>
+ );
+ return html;
+ }
+}
+
+
+function buildRadioButtons(props) {
+ let className = 'sqCheckBox';
+ return(
+ <div className={className}>
+ {
+ props.options.map((o,i) => {
+ let label = o.label || o;
+ let value = o.value || o;
+ return (
+ <label key={i}>
+ {label}
+ <input type="radio" checked={props.value == value} value={value} onChange={props.onChange} />
+ </label>
+ )
+ })
+ }
+ </div>
+
+ )
+}
+
+Input.defaultProps = {
+ onChange: function(e) {
+ console.log(e.target.value, e);
+ console.dir(e.target);
+ },
+ label: '',
+ defaultValue: null,
+ type: 'text',
+ readonly: false,
+ style:{},
+ className: ''
+
+}
+
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
render() {
let html;
let defaultValue = this.props.defaultValue;
- let options = this.props.options.map(function(op, i) {
- let value = JSON.stringify(op.value);
- return <option key={i} value={JSON.stringify(op.value)}>{op.label}</option>
- });
+ let options = this.props.options && this.props.options.map(function(op, i) {
+ let value;
+ let label;
+ if(typeof(op) == 'object') {
+ value = JSON.stringify(op.value);
+ label = op.label;
+ } else {
+ value = op;
+ label = op;
+ }
+
+ return <option key={i} value={JSON.stringify(value)}>{label}</option>
+ }) || [];
if (this.props.initial) {
options.unshift(<option key='blank' value={JSON.stringify(this.props.defaultValue)}></option>);
}
html = (
- <label>
- {this.props.label}
- <select className={this.props.className} onChange={this.handleOnChange} defaultValue={JSON.stringify(defaultValue)} >
- {
- options
- }
- </select>
+ <label key={this.props.key} className={this.props.className}>
+ <span>{this.props.label}</span>
+ {
+ this.props.readonly ? defaultValue
+ : (
+ <select
+ className={this.props.className}
+ onChange={this.handleOnChange}
+ value={JSON.stringify(this.props.value)}
+ defaultValue={JSON.stringify(defaultValue)}>
+ {
+ options
+ }
+ </select>
+ )
+ }
</label>
);
return html;
}
}
SelectOption.defaultProps = {
+ /**
+ * [options description]
+ * @type {Array} - Expects items to contain objects with the properties 'label' and 'value' which are both string types. Hint: JSON.stringify()
+ */
options: [],
onChange: function(e) {
+ console.log(e.target.value)
console.dir(e)
},
- defaultValue: false,
+ readonly: false,
+ /**
+ * Selected or default value
+​
+ * @type {[type]}
+ */
+ defaultValue: null,
+ /**
+ * True if first entry in dropdown should be blank
+ * @type {Boolean}
+ */
initial: false,
label: null
}
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
import './formControls.scss';
import React, {Component} from 'react';
-
-export default class TextInput extends Component {
- render() {
- let {label, onChange, value, defaultValue, ...props} = this.props;
- let inputProperties = {
- value: value,
- onChange: onChange
- }
- let isRequired;
- let inputType;
- if(this.props.required) {
- isRequired = <span className="required">*</span>
- }
- if (defaultValue) {
- inputProperties.defaultValue = defaultValue;
- }
- if (props.pattern) {
- inputProperties.pattern = props.pattern;
- }
- if (value == undefined) {
- value = defaultValue;
- }
- switch(props.type) {
- case 'textarea':
- inputType = <textarea {...inputProperties} value={value}/>
-
- break;
- default:
- inputType = <input type={props.type} {...inputProperties} placeholder={props.placeholder}/>;
- }
- let html = (
- <label className={"sqTextInput " + props.className} style={props.style}>
- <span> { label } {isRequired}</span>
- {
- !props.readonly ? inputType : <div className="readonly">{value}</div>
- }
-
- </label>
- );
- return html;
+import Input from './input.jsx';
+class TextInput extends Input {
+ constructor(props) {
+ super(props);
+ console.warn('TextInput is deprecated. Use Input component instead')
}
}
-
-TextInput.defaultProps = {
- onChange: function(e) {
- console.log(e.target.value);
- },
- label: '',
- defaultValue: undefined,
- type: 'text',
- readonly: false,
- style:{}
-
-}
+export default TextInput;
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
*/
header.header-app-component {
- padding: 20px 0px;
+ padding: 10px 0px;
+ display:-ms-flexbox;
display:flex;
- flex-direction:column;
+ -ms-flex-direction:column;
+ flex-direction:column;
.header-app-main {
+ display:-ms-flexbox;
display:flex;
- flex-direction:row;
- justify-content:space-between;
- align-items:center;
+ -ms-flex-direction:row;
+ flex-direction:row;
+ -ms-flex-pack:justify;
+ justify-content:space-between;
+ -ms-flex-align:center;
+ align-items:center;
}
h1 {
/*background: url('../../style/img/header-logo.png') no-repeat;*/
font-size: 1.625rem;
font-weight: 400;
position:relative;
- flex: 1 0 auto;
+ -ms-flex: 1 0 auto;
+ flex: 1 0 auto;
}
ul {
+ display:-ms-flexbox;
display:flex;
}
li {
+ display:-ms-flexbox;
display:flex;
- flex:1 1 auto;
+ -ms-flex:1 1 auto;
+ flex:1 1 auto;
border-right:1px solid #e5e5e5;
padding: 0 1rem;
&:last-child {
}
a {
cursor:pointer;
- // padding: 0.125rem;
- // border-bottom:1px solid black;
+ /* padding: 0.125rem;*/
+ /* border-bottom:1px solid black;*/
text-decoration:underline;
}
}
.header-app-nav {
+ display:-ms-flexbox;
display:flex;
margin-left: 0.25rem;
a,span {
}
}
nav {
+ display:-ms-flexbox;
display:flex;
- flex:0 1 auto;
- align-items:center;
+ -ms-flex:0 1 auto;
+ flex:0 1 auto;
+ -ms-flex-align:center;
+ align-items:center;
}
}
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
}
render() {
if(!this.props.hasFailed) {
- return (<span className='throttledMessageText'>{this.state.displayMessage}</span>)
+ return (<span className='throttledMessageText' style={{margin:'1rem'}}>{this.state.displayMessage}</span>)
} else {
return (<span> </span>)
}
import React, {Component} from 'react';
import 'style/core.css';
import './panel.scss';
+import circleXImage from '../../../node_modules/open-iconic/svg/circle-x.svg';
export class Panel extends Component {
constructor(props) {
super(props)
let classRoot = className ? ' ' + className : ' ';
let hasCorners = this.props['no-corners'];
let titleTag = title ? <header className="skyquakePanel-title">{title}</header> : '';
+ let closeButton = (
+ <a onClick={self.props.hasCloseButton}
+ className={"close-btn"}>
+ <img src={circleXImage} title="Close card" />
+ </a>
+ );
return (
<section className={'skyquakePanel' + classRoot} style={props.style}>
+ { self.props.hasCloseButton ? closeButton : null}
{ !hasCorners ? <i className="corner-accent top left"></i> : null }
{ !hasCorners ? <i className="corner-accent top right"></i> : null }
{titleTag}
export class PanelWrapper extends Component {
render() {
+ let wrapperClass = 'skyquakePanelWrapper';
+ let {className, column, style, ...props} = this.props;
+ if(className) {
+ wrapperClass = `${wrapperClass} ${className}`
+ }
+ if(column) {
+ style.flexDirection = 'column';
+ }
return (
- <div className={'skyquakePanelWrapper ' + this.props.className} style={this.props.style}>
+ <div className={wrapperClass} style={style} {...props}>
{this.props.children}
</div>)
}
}
-
+PanelWrapper.defaultProps = {
+ style: {}
+}
export default Panel;
width:100%;
height:100%;
}
+ .close-btn {
+ cursor:pointer;
+ position: absolute;
+ right: -0.5rem;
+ top: -0.5rem;
+ img {
+ width: 1rem;
+ }
+ }
}
.skyquakePanelWrapper.column {
+ -ms-flex-direction:column;
+ flex-direction:column;
+ display:-ms-flexbox;
+ display:flex;
.skyquakePanel-wrapper {
height:auto;
}
*
*/
- /**
- * EventCenter module to display a list of events from the system
- * @module framework/widgets/skyquake_container/EventCenter
- * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
- *
- */
+/**
+ * EventCenter module to display a list of events from the system
+ * @module framework/widgets/skyquake_container/EventCenter
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ *
+ */
import React from 'react';
import { Link } from 'react-router';
import _isEqual from 'lodash/isEqual';
import _merge from 'lodash/merge';
import _indexOf from 'lodash/indexOf';
+import _isArray from 'lodash/isArray';
+import _sortBy from 'lodash/sortBy';
import '../../../node_modules/react-treeview/react-treeview.css';
import './eventCenter.scss';
componentWillReceiveProps(props) {
let stateObject = {};
- let notificationList = sessionStorage.getItem('notificationList');
let latestNotification = sessionStorage.getItem('latestNotification');
if (props.newNotificationEvent && props.newNotificationMsg) {
stateObject.newNotificationEvent = false;
stateObject.newNotificationMsg = null;
}
+ stateObject.notifications = props.notifications;
- if (notificationList) {
- stateObject.notifications = _merge(notificationList, props.notifications);
- } else {
- stateObject.notifications = props.notifications;
- }
- sessionStorage.setItem('notifications', JSON.stringify(stateObject.notifications));
this.setState(stateObject);
}
newNotificationReset = () => {
- this.setState({
- newNotificationEvent: false
- });
- }
+ this.setState({
+ newNotificationEvent: false
+ });
+ }
onClickToggleOpenClose(event) {
this.props.onToggle();
constructTree(details) {
let markup = [];
for (let key in details) {
- if (typeof(details[key]) == "object") {
- let html = (
- <TreeView key={key} nodeLabel={key}>
- {this.constructTree(details[key])}
- </TreeView>
- );
- markup.push(html);
+ if (typeof (details[key]) == "object") {
+ let html = (
+ <TreeView key={key} nodeLabel={key}>
+ {this.constructTree(details[key])}
+ </TreeView>
+ );
+ markup.push(html);
} else {
markup.push((<div key={key} className="info">{key} = {details[key].toString()}</div>));
}
);
}
- this.state.notifications && this.state.notifications.map((notification, notifIndex) => {
- let notificationFields = {};
-
- notificationFields = this.getNotificationFields(notification);
-
- displayNotifications.push(
- <tr key={notifIndex} className='notificationItem'>
- <td className='source column'> {notificationFields.source} </td>
- <td className='timestamp column'> {notificationFields.eventTime} </td>
- <td className='event column'> {notificationFields.eventKey} </td>
- <td className='details column'>
- <TreeView key={notifIndex + '-detail'} nodeLabel='Event Details'>
- {this.constructTree(notificationFields.details)}
- </TreeView>
- </td>
- </tr>
- );
- });
+ this.state.notifications &&
+ _isArray(this.state.notifications) &&
+ this.state.notifications.map((notification, notifIndex) => {
+ let notificationFields = {};
+
+ notificationFields = this.getNotificationFields(notification);
+
+ displayNotifications.push(
+ <tr key={notifIndex} className='notificationItem'>
+ <td className='source column'> {notificationFields.source} </td>
+ <td className='timestamp column'> {notificationFields.eventTime} </td>
+ <td className='event column'> {notificationFields.eventKey} </td>
+ <td className='details column'>
+ <TreeView key={notifIndex + '-detail'} nodeLabel='Event Details'>
+ {this.constructTree(notificationFields.details)}
+ </TreeView>
+ </td>
+ </tr>
+ );
+ });
let openedClassName = this.state.isOpen ? 'open' : 'closed';
html = (
<div className={'eventCenter ' + openedClassName}>
- <div className='notification'>
- <Crouton
- id={Date.now()}
- message={this.getNotificationFields(this.state.newNotificationMsg).eventKey +
- ' notification received. Check Event Center for more details.'}
- type={'info'}
- hidden={!(this.state.newNotificationEvent && this.state.newNotificationMsg)}
- onDismiss={this.newNotificationReset}
- timeout={this.props.dismissTimeout}
- />
- </div>
+ <div className='notification'>
+ <Crouton
+ id={Date.now()}
+ message={this.getNotificationFields(this.state.newNotificationMsg).eventKey +
+ ' notification received. Check Event Center for more details.'}
+ type={'info'}
+ hidden={!(this.state.newNotificationEvent && this.state.newNotificationMsg)}
+ onDismiss={this.newNotificationReset}
+ timeout={this.props.dismissTimeout}
+ />
+ </div>
<h1 className='title' data-open-close-icon={this.state.isOpen ? 'open' : 'closed'}
onClick={this.onClickToggleOpenClose.bind(this)}>
EVENT CENTER
.crouton {
span {
- white-space: pre;
+ /* white-space: pre;*/
}
}
.skyquakeApp {
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
-ms-flex-direction: column;
- flex-direction: column;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ flex-direction: column;
height: 100%;
background: $gray-lightest;
h1 {
}
.skyquakeNav {
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
color:white;
background:black;
font-size:0.75rem;
.secondaryNav {
-ms-flex: 1 1 auto;
- flex: 1 1 auto;
+ -webkit-box-flex: 1;
+ flex: 1 1 auto;
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
-ms-flex-pack: end;
- justify-content: flex-end;
+ -webkit-box-pack: end;
+ justify-content: flex-end;
}
.app {
position:relative;
font-size:0.75rem;
border-right: 1px solid black;
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
-ms-flex-align: center;
- align-items: center;
+ -webkit-box-align: center;
+ align-items: center;
.oi {
padding-right: 0.5rem;
}
display:none;
z-index:2;
width: 100%;
+ &.project {
+ a {
+ text-transform:none;
+ }
+ }
}
&:first-child{
h2 {
&:before {
content: '';
height:1.85rem;
- width:5.5rem;
- /*margin:0 1rem;*/
- /*padding:0 0.125rem;*/
+ width:2.5rem;
+ margin:auto 1rem;
+ padding:0 0.125rem;
+ /* background: url('../../style/img/header-logo.png') no-repeat center center white;*/
/*background: url('../../style/img/svg/riftio_logo_white.svg') no-repeat center center;*/
background: url('../../style/img/svg/osm-logo_color_rgb_white_text.svg') no-repeat center center;
- background-size: contain;
+ background-size:contain;
}
}
.skyquakeContainerWrapper {
text-align:left;
position:relative;
-ms-flex: 1 0 auto;
- flex: 1 0 auto;
+ -webkit-box-flex: 1;
+ flex: 1 0 auto;
}
}
.corner-accent {
this.actions = context.flux.actions.global;
}
render(props) {
- return <Component {...this.props} router={this.router} actions={this.actions} flux={this.context.flux} />
+ return <Component {...this.props} router={this.router} actions={this.actions} flux={this.context.flux}/>
}
}
SkyquakeComponent.contextTypes = {
import React from 'react';
import AltContainer from 'alt-container';
import Alt from './skyquakeAltInstance.js';
-import SkyquakeNav from './skyquakeNav.jsx';
+ import _cloneDeep from 'lodash/cloneDeep';
+import SkyquakeNav from '../skyquake_nav/skyquakeNav.jsx';
import EventCenter from './eventCenter.jsx';
import SkyquakeContainerActions from './skyquakeContainerActions.js'
import SkyquakeContainerStore from './skyquakeContainerStore.js';
// import Breadcrumbs from 'react-breadcrumbs';
import Utils from 'utils/utils.js';
import Crouton from 'react-crouton';
+import SkyquakeNotification from '../skyquake_notification/skyquakeNotification.jsx';
import ScreenLoader from 'widgets/screen-loader/screenLoader.jsx';
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
import './skyquakeApp.scss';
// import 'style/reset.css';
import 'style/core.css';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
export default class skyquakeContainer extends React.Component {
constructor(props) {
super(props);
this.state.eventCenterIsOpen = false;
this.state.currentPlugin = SkyquakeContainerStore.currentPlugin;
}
-
+ getChildContext() {
+ return {
+ userProfile: this.state.user
+ };
+ }
+ getUserProfile() {
+ return this.state.user;
+ }
componentWillMount() {
let self = this;
Utils.bootstrapApplication().then(function() {
SkyquakeContainerStore.listen(self.listener);
SkyquakeContainerStore.getNav();
+ SkyquakeContainerStore.getUserProfile();
SkyquakeContainerStore.getEventStreams();
});
}
render() {
- const {displayNotification, notificationMessage, displayScreenLoader, notificationType, ...state} = this.state;
+ const {displayNotification, notificationData, displayScreenLoader,...state} = this.state;
+ const User = this.state.user || {};
+ const rbacValid = isRBACValid(User, [PLATFORM.SUPER, PLATFORM.ADMIN, PLATFORM.OPER]);
var html;
-
+ let nav = _cloneDeep(this.state.nav);
if (this.matchesLoginUrl()) {
html = (
- <AltContainer>
+ <AltContainer flux={Alt}>
<div className="skyquakeApp">
{this.props.children}
</div>
html = (
<AltContainer flux={Alt}>
<div className="skyquakeApp wrap">
- <Crouton
- id={Date.now()}
- message={notificationMessage}
- type={notificationType}
- hidden={!(displayNotification && notificationMessage)}
+ <SkyquakeNotification
+ data={this.state.notificationData}
+ visible={displayNotification}
onDismiss={SkyquakeContainerActions.hideNotification}
- timeout= {5000}
/>
<ScreenLoader show={displayScreenLoader}/>
- <SkyquakeNav nav={this.state.nav}
+ <SkyquakeNav nav={nav}
currentPlugin={this.state.currentPlugin}
- store={SkyquakeContainerStore} />
+ currentUser={this.state.user.userId}
+ currentProject={this.state.user.projectId}
+ store={SkyquakeContainerStore}
+ projects={this.state.projects} />
<div className="titleBar">
- <h1>{this.state.currentPlugin + tag}</h1>
+ <h1>{(this.state.nav.name ? this.state.nav.name.replace('_', ' ').replace('-', ' ') : this.state.currentPlugin && this.state.currentPlugin.replace('_', ' ').replace('-', ' ')) + tag}</h1>
</div>
<div className={"application " + routeName}>
{this.props.children}
</div>
- <EventCenter className="eventCenter"
- notifications={this.state.notifications}
- newNotificationEvent={this.state.newNotificationEvent}
- newNotificationMsg={this.state.newNotificationMsg}
- onToggle={this.onToggle} />
+ {
+ rbacValid ?
+ <EventCenter className="eventCenter"
+ notifications={this.state.notifications}
+ newNotificationEvent={this.state.newNotificationEvent}
+ newNotificationMsg={this.state.newNotificationMsg}
+ onToggle={this.onToggle} />
+ : null
+ }
</div>
</AltContainer>
);
return html;
}
}
+skyquakeContainer.childContextTypes = {
+ userProfile: React.PropTypes.object
+};
skyquakeContainer.contextTypes = {
router: React.PropTypes.object
};
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
'getEventStreamsSuccess',
'getEventStreamsError',
//Notifications
+ 'handleServerReportedError',
'showNotification',
'hideNotification',
//Screen Loader
'showScreenLoader',
- 'hideScreenLoader'
+ 'hideScreenLoader',
+ 'openProjectSocketSuccess',
+ 'getUserProfileSuccess',
+ 'selectActiveProjectSuccess'
);
remote: function(state, location, streamSource) {
return new Promise((resolve, reject) => {
$.ajax({
- url: '//' + window.location.hostname + ':' + window.location.port + '/socket-polling?api_server=' + API_SERVER,
+ url: '//' + window.location.hostname + ':' + window.location.port + '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
success: SkyquakeContainerActions.openNotificationsSocketSuccess,
error: SkyquakeContainerActions.openNotificationsSocketError
}
+ },
+ openProjectSocket() {
+ return {
+ remote: function(state) {
+ return new Promise(function(resolve, reject) {
+ //If socket connection already exists, eat the request.
+ if(state.socket) {
+ return resolve(false);
+ }
+ $.ajax({
+ url: '/socket-polling',
+ type: 'POST',
+ beforeSend: Utils.addAuthorizationStub,
+ data: {
+ url: '/project?api_server=' + API_SERVER
+ },
+ success: function(data, textStatus, jqXHR) {
+ Utils.checkAndResolveSocketRequest(data, resolve, reject);
+ }
+ })
+ .fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ });;
+ });
+ },
+ success: SkyquakeContainerActions.openProjectSocketSuccess
+ }
+ },
+
+ getUserProfile() {
+ return {
+ remote: function(state, recordID) {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: '/user-profile?api_server=' + API_SERVER,
+ type: 'GET',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data) {
+ resolve(data);
+ }
+ }).fail(function(xhr) {
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ });;
+ });
+ },
+ loading: Alt.actions.global.showScreenLoader,
+ success: SkyquakeContainerActions.getUserProfileSuccess
+ }
+ },
+
+ selectActiveProject() {
+ return {
+ remote: function(state, projectId) {
+ const {currentPlugin} = state;
+ const encodedProjectId = encodeURIComponent(projectId);
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/session/${encodedProjectId}?api_server=${API_SERVER}&app=${currentPlugin}`,
+ type: 'PUT',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data) {
+ resolve(projectId);
+ }
+ }).fail(function(xhr) {
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ });;
+ });
+ },
+ success: SkyquakeContainerActions.selectActiveProjectSuccess
+ }
}
}
import Alt from './skyquakeAltInstance.js';
import SkyquakeContainerSource from './skyquakeContainerSource.js';
import SkyquakeContainerActions from './skyquakeContainerActions';
+let Utils = require('utils/utils.js');
import _indexOf from 'lodash/indexOf';
+import _isEqual from 'lodash/isEqual';
//Temporary, until api server is on same port as webserver
import rw from 'utils/rw.js';
var API_SERVER = rw.getSearchParams(window.location).api_server;
-var UPLOAD_SERVER = rw.getSearchParams(window.location).upload_server;
+const MAX_STORED_EVENTS = 20;
class SkyquakeContainerStore {
constructor() {
this.currentPlugin = getCurrentPlugin();
this.nav = {};
- this.notifications = [];
+ let notificationList = null;
+ try {notificationList = JSON.parse(sessionStorage.getItem('notifications'));} catch (e) {}
+ this.notifications = notificationList || [];
this.socket = null;
+ this.projects = null;
+ this.user = {};
//Notification defaults
this.notificationMessage = '';
this.displayNotification = false;
ws.onmessage = (socket) => {
try {
var data = JSON.parse(socket.data);
+ if(data.hasOwnProperty('map')) {
+ data = [];
+ }
if (!data.notification) {
console.warn('No notification in the received payload: ', data);
} else {
// newly appreared event.
// Add to the notifications list and setState
self.notifications.unshift(data.notification);
+ (self.notifications.length > MAX_STORED_EVENTS) && self.notifications.pop();
self.setState({
newNotificationEvent: true,
newNotificationMsg: data.notification,
notifications: self.notifications,
isLoading: false
});
+ sessionStorage.setItem('notifications', JSON.stringify(self.notifications));
}
}
} catch(e) {
console.log('Found streams: ', streams);
let self = this;
+ streams &&
streams['ietf-restconf-monitoring:streams'] &&
streams['ietf-restconf-monitoring:streams']['stream'] &&
streams['ietf-restconf-monitoring:streams']['stream'].map((stream) => {
})
}
- //Notifications
- showNotification = (data) => {
- let state = {
- displayNotification: true,
- notificationMessage: data,
- notificationType: 'error',
- displayScreenLoader: false
- }
- if(typeof(data) == 'string') {
-
- } else {
- state.notificationMessage = data.msg;
- if(data.type) {
- state.notificationType = data.type;
+ openProjectSocketSuccess = (connection) => {
+ var self = this;
+ var ws = window.multiplexer.channel(connection);
+ if (!connection) return;
+ self.setState({
+ socket: ws.ws,
+ channelId: connection
+ });
+ ws.onmessage = function(socket) {
+ try {
+ var data = JSON.parse(socket.data);
+ Utils.checkAuthentication(data.statusCode, function() {
+ self.closeSocket();
+ });
+ if (!data.project || !_isEqual(data.project, self.projects)) {
+ let user = self.user;
+ user.projects = data.project;
+ self.setState({
+ user: user,
+ projects: data.project || {}
+ });
+ }
+ } catch(e) {
+ console.log('HIT an exception in openProjectSocketSuccess', e);
}
- }
- this.setState(state);
+ };
+ }
+ getUserProfileSuccess = (user) => {
+ this.alt.actions.global.hideScreenLoader.defer();
+ this.setState({user})
+ }
+ selectActiveProjectSuccess = (projectId) => {
+ let user = this.user;
+ user.projectId = projectId;
+ this.setState({user});
+ window.location.href = window.location.origin;
+ }
+ //Notifications
+ handleServerReportedError = (result) => {
+ this.hideScreenLoader();
+ this.alt.actions.global.showNotification.defer(result);
+ }
+ showNotification = (notificationData) => {
+ this.setState({
+ notificationData,
+ displayNotification: true
+ })
}
hideNotification = () => {
this.setState({
if (k != currentPlugin) {
nav[k].routes.map(function(route, i) {
if (API_SERVER) {
- route.route = '/' + k + '/index.html?api_server=' + API_SERVER + '&upload_server=' + UPLOAD_SERVER + '#' + route.route;
+ route.route = '/' + k + '/index.html?api_server=' + API_SERVER + '#' + route.route;
} else {
route.route = '/' + k + '/#' + route.route;
}
+++ /dev/null
-/*
- *
- * Copyright 2016 RIFT.IO Inc
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import React from 'react';
-import { Link } from 'react-router';
-import Utils from 'utils/utils.js';
-import Crouton from 'react-crouton';
-import 'style/common.scss';
-
-//Temporary, until api server is on same port as webserver
-import rw from 'utils/rw.js';
-
-var API_SERVER = rw.getSearchParams(window.location).api_server;
-var UPLOAD_SERVER = rw.getSearchParams(window.location).upload_server;
-
-//
-// Internal classes/functions
-//
-
-class LogoutAppMenuItem extends React.Component {
- handleLogout() {
- Utils.clearAuthentication();
- }
- render() {
- return (
- <div className="app">
- <h2>
- <a onClick={this.handleLogout}>
- Logout
- </a>
- </h2>
- </div>
- );
- }
-}
-
-
-//
-// Exported classes and functions
-//
-
-//
-/**
- * Skyquake Nav Component. Provides navigation functionality between all plugins
- */
-export default class skyquakeNav extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
- this.state.validateErrorEvent = 0;
- this.state.validateErrorMsg = '';
- }
- validateError = (msg) => {
- this.setState({
- validateErrorEvent: true,
- validateErrorMsg: msg
- });
- }
- validateReset = () => {
- this.setState({
- validateErrorEvent: false
- });
- }
- returnCrouton = () => {
- return <Crouton
- id={Date.now()}
- message={this.state.validateErrorMsg}
- type={"error"}
- hidden={!(this.state.validateErrorEvent && this.state.validateErrorMsg)}
- onDismiss={this.validateReset}
- />;
- }
- render() {
- let html;
- html = (
- <div>
- {this.returnCrouton()}
- <nav className="skyquakeNav">
- {buildNav.call(this, this.props.nav, this.props.currentPlugin)}
- </nav>
-
- </div>
- )
- return html;
- }
-}
-skyquakeNav.defaultProps = {
- nav: {}
-}
-/**
- * Returns a React Component
- * @param {object} link Information about the nav link
- * @param {string} link.route Hash route that the SPA should resolve
- * @param {string} link.name Link name to be displayed
- * @param {number} index index of current array item
- * @return {object} component A React LI Component
- */
-//This should be extended to also make use of internal/external links and determine if the link should refer to an outside plugin or itself.
-export function buildNavListItem (k, link, index) {
- let html = false;
- if (link.type == 'external') {
- this.hasSubNav[k] = true;
- html = (
- <li key={index}>
- {returnLinkItem(link)}
- </li>
- );
- }
- return html;
-}
-
-/**
- * Builds a link to a React Router route or a new plugin route.
- * @param {object} link Routing information from nav object.
- * @return {object} component returns a react component that links to a new route.
- */
-export function returnLinkItem(link) {
- let ref;
- let route = link.route;
- if(link.isExternal) {
- ref = (
- <a href={route}>{link.label}</a>
- )
- } else {
- if(link.path && link.path.replace(' ', '') != '') {
- route = link.path;
- }
- if(link.query) {
- let query = {};
- query[link.query] = '';
- route = {
- pathname: route,
- query: query
- }
- }
- ref = (
- <Link to={route}>
- {link.label}
- </Link>
- )
- }
- return ref;
-}
-
-/**
- * Constructs nav for each plugin, along with available subnavs
- * @param {array} nav List returned from /nav endpoint.
- * @return {array} List of constructed nav element for each plugin
- */
-export function buildNav(nav, currentPlugin) {
- let navList = [];
- let navListHTML = [];
- let secondaryNav = [];
- let self = this;
- self.hasSubNav = {};
- let secondaryNavHTML = (
- <div className="secondaryNav" key="secondaryNav">
- {secondaryNav}
- <LogoutAppMenuItem />
- </div>
- )
- for (let k in nav) {
- if (nav.hasOwnProperty(k)) {
- self.hasSubNav[k] = false;
- let header = null;
- let navClass = "app";
- let routes = nav[k].routes;
- let navItem = {};
- //Primary plugin title and link to dashboard.
- let route;
- let NavList;
- if (API_SERVER) {
- route = routes[0].isExternal ? '/' + k + '/index.html?api_server=' + API_SERVER + '' + '&upload_server=' + UPLOAD_SERVER + '' : '';
- } else {
- route = routes[0].isExternal ? '/' + k + '/' : '';
- }
- let dashboardLink = returnLinkItem({
- isExternal: routes[0].isExternal,
- pluginName: nav[k].pluginName,
- label: nav[k].label || k,
- route: route
- });
- if (nav[k].pluginName == currentPlugin) {
- navClass += " active";
- }
- NavList = nav[k].routes.map(buildNavListItem.bind(self, k));
- navItem.priority = nav[k].priority;
- navItem.order = nav[k].order;
- navItem.html = (
- <div key={k} className={navClass}>
- <h2>{dashboardLink} {self.hasSubNav[k] ? <span className="oi" data-glyph="caret-bottom"></span> : ''}</h2>
- <ul className="menu">
- {
- NavList
- }
- </ul>
- </div>
- );
- navList.push(navItem)
- }
- }
- //Sorts nav items by order and returns only the markup
- navListHTML = navList.sort((a,b) => a.order - b.order).map(function(n) {
- if((n.priority < 2)){
- return n.html;
- } else {
- secondaryNav.push(n.html);
- }
- });
- navListHTML.push(secondaryNavHTML);
- return navListHTML;
-}
from 'react-router';
import SkyquakeContainer from 'widgets/skyquake_container/skyquakeContainer.jsx';
+/**
+ * This is the Skyquake App wrapper that all plugins use to be a Skyquake plugin. This
+ * is not a react component although it does return a react component that will be the
+ * app.
+ * This function will also set the title into the <head>
+ *
+ * @export
+ * @param {any} config
+ * @param {any} context
+ * @returns a react component to be rendered manually.
+ */
export default function(config, context) {
let routes = [];
let index = null;
let components = null;
+
+ document.title = config.name || "OpenMANO";
+
if (config && config.routes) {
routes = buildRoutes(config.routes)
function buildRoutes(routes) {
},
childRoutes: routes
}
-
return((
<Router history={hashHistory} routes={rootRoute}>
</Router>
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react';
+import { Link } from 'react-router';
+import Utils from 'utils/utils.js';
+import Crouton from 'react-crouton';
+import 'style/common.scss';
+
+import './skyquakeNav.scss';
+import SelectOption from '../form_controls/selectOption.jsx';
+import { FormSection } from '../form_controls/formControls.jsx';
+import { isRBACValid, SkyquakeRBAC } from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+
+//Temporary, until api server is on same port as webserver
+import rw from 'utils/rw.js';
+
+var API_SERVER = rw.getSearchParams(window.location).api_server;
+var DOWNLOAD_SERVER = rw.getSearchParams(window.location).dev_download_server;
+
+//
+// Internal classes/functions
+//
+
+class SelectProject extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+ selectProject(e) {
+ let value = JSON.parse(e.currentTarget.value);
+ // console.log('selected project', value)
+ }
+ render() {
+ let props = this.props;
+ let hasProjects = props.projects;
+ let userAssignedProjects = hasProjects && (props.projects.length > 0)
+ return (
+ <div className="app">
+ <h2>
+ <a style={{textTransform:'none'}}>
+ {
+ hasProjects ?
+ (userAssignedProjects ? 'PROJECT: ' + props.currentProject : 'No Projects Assigned')
+ : 'Projects Loading...'
+ }
+ </a>
+ {
+ userAssignedProjects ? <span className="oi" data-glyph="caret-bottom"></span> : null
+ }
+ </h2>
+ {
+ userAssignedProjects ?
+ <ul className="project menu">
+ {
+ props.projects.map(function (p, k) {
+ return <li key={k} onClick={props.onSelectProject.bind(null, p.name)}><a>{p.name}</a></li>
+ })
+ }
+ </ul>
+ : null
+ }
+ </div>
+ )
+ }
+}
+
+/*
+
+ <SelectOption
+ options={projects}
+ value={currentValue}
+ defaultValue={currentValue}
+ onChange={props.onSelectProject}
+ className="projectSelect" />
+
+ */
+
+
+class UserNav extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+ handleLogout() {
+ Utils.clearAuthentication();
+ }
+ selectProject(e) {
+ let value = JSON.parse(e.currentTarget.value)
+ // console.log('selected project', value)
+ }
+ render() {
+ let props = this.props;
+ let userProfileLink = null;
+ this.props.nav['user_management'] && this.props.nav['user_management'].routes.map((r) => {
+ if (r.unique) {
+ userProfileLink = r;
+ }
+ })
+ return !userProfileLink ? null : (
+ <div className="app">
+ <h2 className="username">
+ USERNAME: {returnLinkItem(userProfileLink, props.currentUser)}
+ <span className="oi" data-glyph="caret-bottom"></span>
+ </h2>
+ <ul className="menu">
+ <li>
+ {returnLinkItem(userProfileLink, "My Profile")}
+ </li>
+ <li>
+ <a onClick={this.handleLogout}>
+ Logout
+ </a>
+ </li>
+ </ul>
+ </div>
+ )
+ }
+}
+
+UserNav.defaultProps = {
+ projects: [
+
+ ]
+}
+
+//
+// Exported classes and functions
+//
+
+//
+/**
+ * Skyquake Nav Component. Provides navigation functionality between all plugins
+ */
+export default class skyquakeNav extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ this.state.validateErrorEvent = 0;
+ this.state.validateErrorMsg = '';
+ }
+ componentDidMount() {
+ this.props.store.openProjectSocket();
+ }
+ validateError = (msg) => {
+ this.setState({
+ validateErrorEvent: true,
+ validateErrorMsg: msg
+ });
+ }
+ validateReset = () => {
+ this.setState({
+ validateErrorEvent: false
+ });
+ }
+ returnCrouton = () => {
+ return <Crouton
+ id={Date.now()}
+ message={this.state.validateErrorMsg}
+ type={"error"}
+ hidden={!(this.state.validateErrorEvent && this.state.validateErrorMsg)}
+ onDismiss={this.validateReset}
+ />;
+ }
+ render() {
+ let html;
+ html = (
+ <div>
+ {this.returnCrouton()}
+ <nav className="skyquakeNav">
+ {buildNav.call(this, this.props.nav, this.props.currentPlugin, this.props)}
+ </nav>
+
+ </div>
+ )
+ return html;
+ }
+}
+skyquakeNav.defaultProps = {
+ nav: {}
+}
+skyquakeNav.contextTypes = {
+ userProfile: React.PropTypes.object
+};
+/**
+ * Returns a React Component
+ * @param {object} link Information about the nav link
+ * @param {string} link.route Hash route that the SPA should resolve
+ * @param {string} link.name Link name to be displayed
+ * @param {number} index index of current array item
+ * @return {object} component A React LI Component
+ */
+//This should be extended to also make use of internal/external links and determine if the link should refer to an outside plugin or itself.
+export function buildNavListItem(k, link, index) {
+ let html = false;
+ if (link.type == 'external') {
+ this.hasSubNav[k] = true;
+ html = (
+ <li key={index}>
+ {returnLinkItem(link)}
+ </li>
+ );
+ }
+ return html;
+}
+
+/**
+ * Builds a link to a React Router route or a new plugin route.
+ * @param {object} link Routing information from nav object.
+ * @return {object} component returns a react component that links to a new route.
+ */
+export function returnLinkItem(link, label) {
+ let ref;
+ let route = link.route;
+ if (link.isExternal) {
+ ref = (
+ <a href={route}>{label || link.label}</a>
+ )
+ } else {
+ if (link.path && link.path.replace(' ', '') != '') {
+ route = link.path;
+ }
+ if (link.query) {
+ let query = {};
+ query[link.query] = '';
+ route = {
+ pathname: route,
+ query: query
+ }
+ }
+ ref = (
+ <Link to={route}>
+ {label || link.label}
+ </Link>
+ )
+ }
+ return ref;
+}
+
+
+
+
+/**
+ * Constructs nav for each plugin, along with available subnavs
+ * @param {array} nav List returned from /nav endpoint.
+ * @return {array} List of constructed nav element for each plugin
+ */
+export function buildNav(navData, currentPlugin, props) {
+ let navList = [];
+ let navListHTML = [];
+ let secondaryNav = [];
+ let adminNav = [];
+ //For monitoring when admin panel is active
+ let adminNavList = [];
+ let self = this;
+ const User = this.context.userProfile;
+ //The way the nav is sorting needs to be refactored.
+ let navArray = navData && Object.keys(navData).sort((a, b) => navData[a].order - navData[b].order)
+ self.hasSubNav = {};
+ for (let i = 0; i < navArray.length; i++) {
+ let k = navArray[i];
+ if (navData.hasOwnProperty(k)) {
+ self.hasSubNav[k] = false;
+ let header = null;
+ let navClass = "app";
+ let routes = navData[k].routes;
+ let navItem = {};
+ //Primary plugin title and link to dashboard.
+ let route;
+ let NavList;
+ if (API_SERVER) {
+ route = routes[0].isExternal ?
+ '/' + k + '/index.html?api_server=' + API_SERVER + '' + (DOWNLOAD_SERVER ? '&dev_download_server=' + DOWNLOAD_SERVER : '')
+ : '';
+ } else {
+ route = routes[0].isExternal ? '/' + k + '/' : '';
+ }
+ if(navData[k].route) {
+ route = route + navData[k].route;
+ }
+ let dashboardLink = returnLinkItem({
+ isExternal: routes[0].isExternal,
+ pluginName: navData[k].pluginName,
+ label: navData[k].label || k,
+ route: route
+ });
+ let shouldAllow = navData[k].allow || ['*'];
+ if (navData[k].pluginName == currentPlugin) {
+ navClass += " active";
+ }
+ NavList = navData[k].routes.filter((r) => {
+ const User = self.context.userProfile;
+ const shouldAllow = r.allow || ['*'];
+ return isRBACValid(User, shouldAllow);
+ }).map(buildNavListItem.bind(self, k));
+ navItem.priority = navData[k].priority;
+ navItem.order = navData[k].order;
+ if (navData[k].admin_link) {
+ if (isRBACValid(User, shouldAllow)) {
+ adminNavList.push(navData[k].pluginName);
+ adminNav.push((
+ <li key={navData[k].pluginName}>
+ {dashboardLink}
+ </li>
+ ))
+ }
+ } else {
+ if (isRBACValid(User, shouldAllow)) {
+ navItem.html = (
+ <div key={k} className={navClass}>
+ <h2>{dashboardLink} {self.hasSubNav[k] ? <span className="oi" data-glyph="caret-bottom"></span> : ''}</h2>
+ <ul className="menu">
+ {NavList}
+ </ul>
+ </div>
+ );
+ }
+ navList.push(navItem)
+ }
+
+ }
+ }
+
+ //Sorts nav items by order and returns only the markup
+ navListHTML = navList.map(function (n) {
+ if ((n.priority < 2)) {
+ return n.html;
+ } else {
+ secondaryNav.push(n.html);
+ }
+ });
+ if (adminNav.length) {
+ navListHTML.push(
+ <div key="Adminstration" className={"app " + (adminNavList.indexOf(currentPlugin) > -1 ? 'active' : '')}>
+ <h2>
+ <a>
+ ADMINISTRATION
+ </a>
+ <span className="oi" data-glyph="caret-bottom"></span>
+ </h2>
+ <ul className="menu">
+ {
+ adminNav
+ }
+ </ul>
+ </div>
+ );
+ }
+ let secondaryNavHTML = (
+ <div className="secondaryNav" key="secondaryNav">
+ {secondaryNav}
+ <SelectProject
+ onSelectProject={props.store.selectActiveProject}
+ projects={props.projects}
+ currentProject={props.currentProject} />
+ <UserNav
+ currentUser={props.currentUser}
+ nav={navData} />
+ </div>
+ )
+ // console.log("app admin " + (adminNavList.indexOf(currentPlugin) > -1 ? 'active' : ''))
+ navListHTML.push(secondaryNavHTML);
+ return navListHTML;
+}
--- /dev/null
+@import '../../style/_colors.scss';
+.active {
+ background-color: $brand-blue!important;
+ border-color: $brand-blue!important;
+ color: #fff!important
+ }
+ .skyquakeNav {
+ display: -ms-flexbox;
+ display: -webkit-box;
+ display: flex;
+ color:white;
+ background:black;
+ position:relative;
+ z-index: 10;
+ font-size:0.75rem;
+ padding-right:1rem;
+ .secondaryNav {
+ -ms-flex: 1 1 auto;
+ -webkit-box-flex: 1;
+ flex: 1 1 auto;
+ display: -ms-flexbox;
+ display: -webkit-box;
+ display: flex;
+ -ms-flex-pack: end;
+ -webkit-box-pack: end;
+ justify-content: flex-end;
+
+ .username a{
+ text-transform: none;
+ }
+ }
+ .app {
+ display: -ms-flexbox;
+ display: block;
+ position:relative;
+ margin: auto 0.5rem;
+ h2 {
+ font-size:0.75rem;
+ border-right: 1px solid black;
+ display: -ms-flexbox;
+ display: -webkit-box;
+ display: flex;
+ -ms-flex-pack: start;
+ -ms-flex-pack: start;
+ -webkit-box-pack: start;
+ justify-content: flex-start;
+ -ms-flex-align: center;
+ -webkit-box-align: center;
+ align-items: center;
+ .oi {
+ padding-right: 0.5rem;
+ }
+ }
+ .menu {
+ position:absolute;
+ display:none;
+ z-index:2;
+ width: 100%;
+ li {
+ text-align:left;
+ }
+ }
+ &:first-child{
+ h2 {
+ border-left: 1px solid black;
+ }
+ }
+ &:hover {
+ a {
+ color:$brand-blue-light;
+ cursor:pointer;
+ }
+ .menu {
+ display:block;
+ background:black;
+ a {
+ color:white;
+ }
+ li:hover {
+ a {
+ color:$brand-blue-light;
+ }
+ }
+ }
+ }
+ &.active {
+ color:white;
+ background:black;
+ a {
+ color:white;
+ }
+ }
+ }
+ a{
+ display:block;
+ padding:0.5rem 1rem;
+ text-decoration:none;
+ text-transform:uppercase;
+ text-align:left;
+ color:white;
+ }
+ &:before {
+ content: '';
+ min-width: 5.5rem;
+ margin: 0.125rem 1rem;
+ /*background: url('../../style/img/svg/riftio_logo_white.svg') no-repeat center center;*/
+ background: url('../../style/img/svg/osm-logo_color_rgb_white_text.svg') no-repeat center center;
+ background-size: contain;
+ }
+ .userSection {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ -ms-flex-align:center;
+ -webkit-box-align:center;
+ align-items:center;
+ padding-left: 1rem;
+ text-transform:uppercase;
+ text-align: left;
+ .projectSelect {
+ padding: 0 0.5rem;
+ font-size: 1rem;
+ /* min-width: 75%;*/
+ height: 25px;
+ }
+ }
+ }
--- /dev/null
+const NETCONF_ERRORS = {
+ 'in-use' : {
+ description: 'The request requires a resource that already is in use.'
+ },
+ 'invalid-value' : {
+ description: 'The request specifies an unacceptable value for one or more parameters.'
+ },
+ 'too-big' : {
+ description: 'The request or response (that would be generated) is too large for the implementation to handle.'
+ },
+ 'missing-attribute' : {
+ description: 'An expected attribute is missing.'
+ },
+ 'bad-attribute' : {
+ description: 'An attribute value is not correct; e.g., wrong type, out of range, pattern mismatch.'
+ },
+ 'unknown-attribute' : {
+ description: 'An unexpected attribute is present.'
+ },
+ 'missing-element' : {
+ description: 'An expected element is missing.'
+ },
+ 'bad-element' : {
+ description: 'An element value is not correct; e.g., wrong type, out of range, pattern mismatch.'
+ },
+ 'unknown-element' : {
+ description: 'An unexpected element is present.'
+ },
+ 'unknown-namespace' : {
+ description: 'An unexpected namespace is present.'
+ },
+ 'access-denied' : {
+ description: 'Access to the requested protocol operation or data model is denied because authorization failed.'
+ },
+ 'lock-denied' : {
+ description: 'Access to the requested lock is denied because the lock is currently held by another entity.'
+ },
+ 'resource-denied' : {
+ description: 'Request could not be completed because of insufficient resources.'
+ },
+ 'rollback-failed' : {
+ description: 'Request to roll back some configuration change (via rollback-on-error or <discard-changes> operations) was not completed for some reason.'
+ },
+ 'data-exists' : {
+ description: 'Request could not be completed because the relevant data model content already exists. For example, a "create" operation was attempted on data that already exists.'
+ },
+ 'data-missing' : {
+ description: 'Request could not be completed because the relevant data model content does not exist. For example, a "delete" operation was attempted on data that does not exist.'
+ },
+ 'operation-not-supported' : {
+ description: 'Request could not be completed because the requested operation is not supported by this implementation.'
+ },
+ 'operation-failed' : {
+ description: 'Request could not be completed because the requested operation failed for some reason not covered by any other error condition.'
+ }
+}
+
+export default NETCONF_ERRORS;
--- /dev/null
+import React from 'react';
+import Crouton from 'react-crouton';
+import NETCONF_ERRORS from './netConfErrors.js';
+
+class SkyquakeNotification extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ this.state.displayNotification = props.visible;
+ this.state.notificationMessage = '';
+ this.state.notificationType = 'error';
+ }
+ componentWillReceiveProps(props) {
+ if(props.visible) {
+ this.processMessage(props.data);
+ } else {
+ this.setState({displayNotification: props.visible});
+ }
+ }
+ buildNetconfError(data) {
+ let error = data;
+ try {
+ let info = JSON.parse(data);
+ let rpcError = info.body || info.errorMessage.body || info.errorMessage.error;
+ if (rpcError && typeof rpcError === 'string') {
+ const index = rpcError.indexOf('{');
+ if (index >= 0) {
+ rpcError = JSON.parse(rpcError.substr(index));
+ } else {
+ return rpcError;
+ }
+ }
+ if (!rpcError) {
+ return error;
+ }
+ info = rpcError["rpc-reply"]["rpc-error"];
+ let errorTag = info['error-tag']
+ error = `
+ ${NETCONF_ERRORS[errorTag] && NETCONF_ERRORS[errorTag].description || 'Unknown NETCONF Error'}
+ PATH: ${info['error-path']}
+ INFO: ${JSON.stringify(info['error-info'])}
+ `
+ } catch (e) {
+ console.log('Unexpected string sent to buildNetconfError: ', e);
+ }
+ return error;
+ }
+ processMessage(data) {
+ let state = {
+ displayNotification: true,
+ notificationMessage: data,
+ notificationType: 'error',
+ displayScreenLoader: false
+ }
+ if(typeof(data) == 'string') {
+ //netconf errors will be json strings
+ state.notificationMessage = this.buildNetconfError(data);
+ } else {
+ let message = data.msg || '';
+ if(data.type) {
+ state.notificationType = data.type;
+ }
+ if(data.rpcError){
+ message += " " + this.buildNetconfError(data.rpcError);
+ }
+ state.notificationMessage = message;
+ }
+ console.log('NOTIFICATION: ', state.notificationMessage)
+ this.setState(state);
+ }
+ render() {
+ const {displayNotification, notificationMessage, notificationType, ...state} = this.state;
+ return (
+ <Crouton
+ id={Date.now()}
+ message={notificationMessage}
+ type={notificationType}
+ hidden={!(displayNotification && notificationMessage)}
+ onDismiss={this.props.onDismiss}
+ timeout={10000}
+ />
+ )
+ }
+}
+SkyquakeNotification.defaultProps = {
+ data: {},
+ onDismiss: function(){}
+}
+export default SkyquakeNotification;
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+import React from 'react';
+import ROLES from 'utils/roleConstants.js';
+const PLATFORM = ROLES.PLATFORM;
+
+export function isRBACValid(User, allow, Project){
+ const UserData = User && User.data;
+ if(UserData) {
+ const PlatformRole = UserData.platform.role;
+ const isPlatformSuper = PlatformRole[PLATFORM.SUPER];
+ const isPlatformAdmin = PlatformRole[PLATFORM.ADMIN];
+ const isPlatformOper = PlatformRole[PLATFORM.OPER];
+ const hasRoleAccess = checkForRoleAccess(UserData.project[Project || User.projectId], PlatformRole, allow)//false//(this.props.roles.indexOf(userProfile.projectRole) > -1)
+ if (isPlatformSuper) {
+ return true;
+ } else {
+ if (hasRoleAccess) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+export default class SkyquakeRBAC extends React.Component {
+ constructor(props, context) {
+ super(props);
+ }
+ render() {
+ const User = this.context.userProfile;
+ const UserData = User.data;
+ const Project = this.props.project;
+ let HTML = null;
+ // If user object has platform property then it has been populated by the back end.
+ if(isRBACValid(User, this.props.allow, Project)) {
+ HTML = this.props.children;
+ }
+ return (<div className={this.props.className} style={this.props.style}>{HTML}</div>)
+ }
+}
+SkyquakeRBAC.defaultProps = {
+ allow: [],
+ project: false
+}
+SkyquakeRBAC.contextTypes = {
+ userProfile: React.PropTypes.object
+}
+
+function checkForRoleAccess(project, PlatformRole, allow) {
+ if (allow.indexOf('*') > -1) return true;
+ for (let i = 0; i<allow.length; i++) {
+ if((project && project.role[allow[i]])|| PlatformRole[allow[i]]) {
+ return true
+ }
+ }
+ return false;
+ }
+
+
+
+// export default function(Component) {
+// class SkyquakeRBAC extends React.Component {
+// constructor(props, context) {
+// super(props);
+// }
+// render(props) {
+// console.log(this.context.userProfile)
+// const User = this.context.userProfile.data;
+// // If user object has platform property then it has been populated by the back end.
+// if(User) {
+// const PlatformRole = User.platform.role;
+// const HTML = <Component {...this.props} router={this.router} actions={this.actions} flux={this.context.flux}/>;
+// const isPlatformSuper = PlatformRole[PLATFORM.SUPER];
+// const isPlatformAdmin = PlatformRole[PLATFORM.ADMIN];
+// const isPlatformOper = PlatformRole[PLATFORM.OPER];
+// const hasRoleAccess = false//(this.props.roles.indexOf(userProfile.projectRole) > -1)
+// if (isPlatformSuper || isPlatformOper || isPlatformAdmin) {
+// return HTML
+// } else {
+// if (hasRoleAccess) {
+// return HTML
+// } else {
+// return null;
+// }
+// }
+// }
+// else {
+// return null;
+
+// }
+// }
+// }
+// SkyquakeRBAC.defaultProps = {
+
+// }
+// SkyquakeRBAC.contextTypes = {
+// userProfile: React.PropTypes.object,
+// allowedRoles: []
+// };
+// return SkyquakeRBAC;
+// }
"dependencies": {
"alt": "^0.18.3",
"alt-container": "^1.0.2",
+ "base-64": "^0.1.0",
"bluebird": "^3.4.1",
"body-parser": "^1.14.2",
"cors": "^2.7.1",
"d3": "^3.5.16",
+ "debug": "^3.0.1",
"ejs": "^2.3.4",
"express": "^4.13.3",
"express-session": "^1.13.0",
"jquery": "^2.2.1",
"json2yaml": "^1.1.0",
"lodash": "^4.0.0",
+ "lusca": "^1.5.0",
"minimist": "^1.2.0",
"open-iconic": "^1.1.1",
+ "passport": "^0.3.2",
+ "passport-http-bearer": "^1.0.1",
+ "passport-oauth2": "^1.4.0",
+ "passport-oauth2-middleware": "^1.0.2",
+ "passport-openidconnect": "^0.0.2",
"prismjs": "^1.3.0",
"promise": "^7.1.1",
"react": "^0.14.6",
"request-promise": "^3.0.0",
"require-json": "0.0.1",
"require-reload": "^0.2.2",
- "reset-css": "^2.0.20160720",
"sockjs": "^0.3.17",
"sockjs-client": "^1.1.1",
"underscore": "^1.8.3",
-#
+#
# Copyright 2016 RIFT.IO Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
#
# Author(s): Kiran Kashalkar
# Creation Date: 08/18/2015
-#
+#
##
# DEPENDENCY ALERT
subdirs
about
composer
- config
debug
+ project_management
+ user_management
# goodbyworld
# helloworld
launchpad
accounts
logging
+ redundancy
+ admin
)
rift_add_subdirs(
SUBDIR_LIST
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
url: utils.confdPort(api_server) + APIVersion + '/api/operational/vcs/info?deep',
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
url: utils.confdPort(api_server) + APIVersion + '/api/operational/version?deep',
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
url: utils.confdPort(api_server) + APIVersion + '/api/operational/uptime/uptime',
"root": "public",
"name": "About",
"dashboard": "./about.jsx",
- "order": 99,
+ "order": 15,
"priority":2,
+ "admin_link": true,
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-rbac-platform:platform-admin",
+ "rw-rbac-platform:platform-oper"
+ ],
"routes" : [{
"label": "Dashboard",
"route": "/",
#!/bin/bash
#
-# Copyright 2016 RIFT.IO Inc
+# Copyright 2016-2017 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.
PLUGIN_NAME=about
# 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 --optimize-minimize --progress --config webpack.production.config.js
-echo 'Packaging '$PLUGIN_NAME' using webpack... done'
-echo 'Building plugin '$PLUGIN_NAME'... done'
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
\ No newline at end of file
#!/bin/bash
-#
+#
# Copyright 2016 RIFT.IO Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
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
+tar -cf $bcache_dir/plugins/$plugin/node_modules.tar node_modules package.json -C $source_dir
#cp -Lrp $source_dir/node_modules $bcache_dir/plugins/$plugin/.
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
if (this.state != null) {
var html = (
<div className="table-container-wrapper">
- {fossInfoComponent}
- {uptimeComponent}
<div className="table-container">
- <h2> Version Info </h2>
+ <h2>RIFT.ware Version Info </h2>
<table>
<thead>
<tr>
</tbody>
</table>
</div>
+ {uptimeComponent}
<div className="table-container">
<h2> Component Info </h2>
<table>
</tbody>
</table>
</div>
+ {fossInfoComponent}
+ <div className="table-container">
+ <h2>RIFT.ware Copyright </h2>
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ Open Source Version
+ </td>
+ <td>
+ (c) Copyright 2014 – {new Date().getUTCFullYear()} RIFT.io Inc., All rights reserved
+ </td>
+ <td>
+ <a href="https://support.riftio.com" target="_blank">https://open.riftio.com/</a>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Commercial Version
+ </td>
+ <td>
+ (c) Copyright 2014 – {new Date().getUTCFullYear()} RIFT.io Inc., All rights reserved
+ </td>
+ <td>
+ <a href="https://riftio.com/contact/" target="_blank">https://riftio.com/contact/</a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
</div>
);
} else {
// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
var config = {
devtool: 'source-map',
entry: mainPath,
},
plugins: [
new HtmlWebpackPlugin({
- filename: '../index.html',
- templateContent: '<div id="app"></div>'
+ filename: '../' + htmlFilename,
+ template: frameworkPath + '/plugin-index.html'
})
]
};
var nameSpace = {
cloud: 'cloud',
sdn: 'sdn',
- 'config-agent': 'config-agent'
+ 'config-agent': 'config-agent',
+ 'resource-orchestrator': 'ro-account'
};
+var APIVersion = '/v2'
Accounts.get = function(req) {
return new Promise(function(resolve, reject) {
if (req.params.type || req.params.name) {
})
});
}, function(reason) {
- reject(reason);
- })
+ reject(reason);
+ })
} else {
getAll(req, resolve, reject);
}
Promise.all([
Cloud.get(req),
Sdn.get(req),
- ConfigAgent.get(req)
+ ConfigAgent.get(req),
+ getResourceOrchestrator(req)
]).then(function(result) {
var ReturnData = {
cloud: result[0],
sdn: result[1],
- 'config-agent': result[2]
+ 'config-agent': result[2],
+ 'resource-orchestrator': result[3]
};
ReturnData.cloud.type = 'cloud';
ReturnData.sdn.type = 'sdn';
Accounts.create = updateAccount;
Accounts.delete = deleteAccount;
Accounts.refreshAccountConnectionStatus = refreshAccountConnectionStatus
+
function getAccount(req) {
return new Promise(function(resolve, reject) {
var self = this;
var type = nameSpace[req.params.type];
var url = utils.confdPort(api_server) + '/api/operational/' + type + '/account';
if (id) {
- url += '/' + id;
+ url += '/' + encodeURIComponent(id);
}
_.extend(
requestHeaders,
id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}
);
request({
- url: url + '?deep',
+ url: utils.projectContextUrl(req, url + '?deep'),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var data = req.body;
var requestHeaders = {};
var createData = {};
- var url = utils.confdPort(api_server) + '/api/config/' + type;
+ var url = utils.confdPort(api_server) + '/api/config/' + type //+ '/account';
var method = 'POST'
if (!id) {
- createData = {
- 'account': Array.isArray(data) ? data : [data]
+ createData = {}
+ if (type == 'ro-account') {
+ createData['rw-ro-account:account'] = Array.isArray(data) ? data : [data]
+ } else {
+ createData['account'] = Array.isArray(data) ? data : [data]
}
console.log('Creating ' + type + ' account: ', createData);
} else {
method = 'PUT';
- url += '/account/' + id;
+ url += '/account/' + encodeURIComponent(id);
createData['rw-' + type + ':account'] = Array.isArray(data) ? data : [data];
}
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: url,
+ url: utils.projectContextUrl(req, url),
method: method,
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
var createData = {};
var url = utils.confdPort(api_server) + '/api/config/' + type;
- url += '/account/' + id;
+ url += '/account/' + encodeURIComponent(id);
return new Promise(function(resolve, reject) {
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: url,
+ url: utils.projectContextUrl(req, url),
method: 'DELETE',
headers: requestHeaders,
forever: constants.FOREVER_ON,
})
}
-function refreshAccountConnectionStatus (req) {
+function refreshAccountConnectionStatus(req) {
var api_server = req.query['api_server'];
var Name = req.params.name;
var Type = req.params.type;
cloud: {
label: 'cloud-account',
rpc: 'update-cloud-status'
+ },
+ 'resource-orchestrator': {
+ label: 'ro-account',
+ rpc: 'update-ro-account-status'
}
}
jsonData.input[rpcInfo[Type].label] = Name;
var headers = _.extend({},
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}
);
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/' + rpcInfo[Type].rpc);
+
+ jsonData['input'] = utils.addProjectContextToRPCPayload(req, uri, jsonData['input']);
+
return new Promise(function(resolve, reject) {
request({
- uri: utils.confdPort(api_server) + '/api/operations/' + rpcInfo[Type].rpc,
+ uri: uri,
method: 'POST',
headers: headers,
forever: constants.FOREVER_ON,
});
};
+function getResourceOrchestrator(req, id) {
+ var self = this;
+ var api_server = req.query["api_server"];
+ var accountID = req.params.id || req.params.name;
+
+ return new Promise(function(resolve, reject) {
+ var requestHeaders = {};
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.collection, {
+ 'Authorization': req.session && req.session.authorization
+ }
+ );
+ var urlOp = utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ro-account/account');
+ var urlConfig = utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ro-account-state/account');
+ if(accountID) {
+ urlOp = url + '/' + encodeURIComponent(accountID);
+ urlConfig = url + '/' + encodeURIComponent(accountID);
+ }
+ var allRequests = [];
+ var roOpData = new Promise(function(resolve, reject) {
+ request({
+ url: urlOp,
+ type: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false
+ },
+ function(error, response, body) {
+ var data;
+ if (utils.validateResponse('RoAccount.get', error, response, body, resolve, reject)) {
+ try {
+ data = JSON.parse(response.body).collection['rw-ro-account:account']
+ } catch (e) {
+ console.log('Problem with "RoAccount.get"', e);
+ var err = {};
+ err.statusCode = 500;
+ err.errorMessage = {
+ error: 'Problem with "RoAccount.get": ' + e // + e.toString()
+ }
+ return reject(err);
+ }
+ return resolve({
+ statusCode: response.statusCode,
+ data: data
+ });
+ };
+ }
+ );
+ });
+ var roConfigData = new Promise(function(resolve, reject){
+ request({
+ url: urlConfig,
+ type: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false
+ },
+ function(error, response, body) {
+ var data;
+ if (utils.validateResponse('RoAccount.get', error, response, body, resolve, reject)) {
+ try {
+ data = JSON.parse(response.body).collection['rw-ro-account:account']
+ } catch (e) {
+ console.log('Problem with "RoAccount.get"', e);
+ var err = {};
+ err.statusCode = 500;
+ err.errorMessage = {
+ error: 'Problem with "RoAccount.get": ' + e // + e.toString()
+ }
+ return reject(err);
+ }
+ return resolve({
+ statusCode: response.statusCode,
+ data: data
+ });
+ };
+ }
+ );
+ });
+
+ allRequests.push(roOpData);
+ allRequests.push(roConfigData);
+ Promise.all(allRequests).then(function(data) {
+ var state = data[1].data;
+ var op = data[0].data;
+ var result = [];
+ var dict = {};
+ if (!accountID) {
+ state.map && state.map(function(s){
+ if(s.name != "rift") {
+ dict[s.name] = s;
+ }
+ });
+ op.map && op.map(function(o) {
+ if(o.name != "rift") {
+ dict[o.name] = _.extend(dict[o.name], o);
+ }
+ });
+ Object.keys(dict).map(function(d) {
+ result.push(dict[d]);
+ })
+ } else {
+ result = _.extend(op, state);
+ }
+ resolve({
+ statusCode: 200,
+ data: result
+ })
+ })
+
+ })
+}
+
module.exports = Accounts;
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account'),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + id),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/config/cloud',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/cloud'),
method: 'POST',
headers: requestHeaders,
forever: constants.FOREVER_ON,
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/config/cloud/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/cloud/account/' + id),
method: 'PUT',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/config/cloud/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/cloud/account/' + id),
method: 'DELETE',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + cloudAccount + '/resources?deep',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + cloudAccount + '/resources?deep'),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + cloudAccount + '/pools',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/cloud/account/' + cloudAccount + '/pools'),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + '/api/operational/config-agent/account',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operational/config-agent/account'),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + '/api/operational/config-agent/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operational/config-agent/account/' + id),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + '/api/config/config-agent',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/config-agent'),
method: 'POST',
headers: requestHeaders,
forever: constants.FOREVER_ON,
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + '/api/config/config-agent/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/config-agent/account/' + id),
method: 'PUT',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + '/api/config/config-agent/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/config-agent/account/' + id),
method: 'DELETE',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + '/api/operational/sdn/account?deep',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operational/sdn/account?deep'),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + '/api/operational/sdn/account/' + id + '?deep',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operational/sdn/account/' + id + '?deep'),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + '/api/config/sdn/account',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/sdn/account'),
method: 'POST',
headers: requestHeaders,
forever: constants.FOREVER_ON,
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + '/api/config/sdn/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/sdn/account/' + id),
method: 'PUT',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + '/api/config/sdn/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/sdn/account/' + id),
method: 'DELETE',
headers: requestHeaders,
forever: constants.FOREVER_ON,
"root": "public",
"name": "Accounts",
"dashboard": "./account/accountsDashboard.jsx",
- "order": 1,
+ "order": 3,
"priority":1,
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-project:project-admin",
+ "rw-project:project-oper",
+ "rw-project-mano:account-oper",
+ "rw-project-mano:account-admin"
+ ],
"routes": [
{
- "label": "Accounts Dashboard",
+ "label": "Dashboard",
"route": "accounts",
"component": "./account/accountsDashboard.jsx",
"path": "accounts",
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+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);
+
+module.exports = app;
var app = require('express').Router();
var cors = require('cors');
-var utils = require('../../framework/core/api_utils/utils.js')
-var accountsAPI = require('./api/accounts.js')
+var utils = require('../../framework/core/api_utils/utils.js');
+var accountsAPI = require('./api/accounts.js');
// Begin Accounts API
app.get('/all', cors(), function(req, res) {
accountsAPI.get(req).then(function(data) {
utils.sendErrorResponse(error, res);
});
})
+ //RO config routes
+
utils.passThroughConstructor(app);
module.exports = app;
PLUGIN_NAME=accounts
# 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 --optimize-minimize --progress --config webpack.production.config.js
-echo 'Packaging '$PLUGIN_NAME' using webpack... done'
-echo 'Building plugin '$PLUGIN_NAME'... done'
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
#!/bin/bash
-#
+#
# Copyright 2016 RIFT.IO Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
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
+tar -cf $bcache_dir/plugins/$plugin/node_modules.tar node_modules package.json -C $source_dir
#cp -Lrp $source_dir/node_modules $bcache_dir/plugins/$plugin/.
import React from 'react';
import Button from 'widgets/button/rw.button.js';
import _cloneDeep from 'lodash/cloneDeep';
+import _isEmpty from 'lodash/isEmpty';
import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
import Crouton from 'react-crouton';
import TextInput from 'widgets/form_controls/textInput.jsx';
import Utils from 'utils/utils.js';
import 'style/common.scss';
import './account.scss';
+
+function isAccessDenied (error) {
+ const rpcResult = Utils.rpcError(error);
+ return rpcResult && rpcResult['rpc-reply']['rpc-error']['error-tag'] === 'access-denied';
+}
+
class Account extends React.Component {
constructor(props) {
super(props);
- this.state = {};
- this.state.account = {};
+
+ // console.log(this.state.account)
}
storeListener = (state) => {
- if(!state.account) {
- this.setUp(this.props)
+ if(state.socket) {
+ if((!state.account || _isEmpty(state.account)) && state.userProfile) {
+ this.setUp(this.props, state.savedData)
+ }
+ state.account && state.account.params && this.setState({
+ account: state.account,
+ accountType: state.accountType,
+ types: state.types,
+ sdnOptions: state.sdnOptions,
+ savedData: state.savedData,
+ userProfile: state.userProfile
+ })
}
- state.account && this.setState({account: state.account,accountType: state.accountType, types: state.types, sdnOptions: state.sdnOptions})
}
componentWillMount() {
+ this.state = this.props.store.getState();
this.props.store.listen(this.storeListener);
- this.setUp(this.props);
+ }
+ componentWillUpdate(nextProps, nextState, nextContext) {
+ if(!_isEmpty(nextContext.userProfile) && !nextState.userProfile) {
+ this.props.store.getTransientAccountForUser(nextContext.userProfile)
+ }
}
componentWillReceiveProps(nextProps) {
if(JSON.stringify(nextProps.params) != JSON.stringify(this.props.params)){
}
}
componentWillUnmount() {
+ console.log('unmounting')
+ // this.setState({account: null, accountType: null, types: []})
this.props.store.unlisten(this.storeListener);
}
- setUp(props){
- if(props.params.name != 'create') {
- this.props.store.viewAccount({type: props.params.type, name: props.params.name});
+ setUp(props, savedData){
+ console.log('Setting up');
+ var SD = savedData || this.state.savedData;
+ if(props.params.name && props.params.name != 'create') {
+ if (SD && SD == props.params.name) {
+ this.props.store.viewAccount({type: props.params.type, name: props.params.name}, SD);
+ } else {
+ this.props.store.viewAccount({type: props.params.type, name: props.params.name});
+ }
+
} else {
- this.props.store.setAccountTemplate(props.params.type);
+ this.props.store.setAccountTemplate(props.params.type, null, SD);
}
}
create(e) {
self.props.flux.actions.global.showNotification("Please give the account a name");
return;
} else {
- var type = Account['account-type'];
- var params = Account.params;
-
- if(params) {
- for (var i = 0; i < params.length; i++) {
- var param = params[i].ref;
- if (typeof(Account[type]) == 'undefined' || typeof(Account[type][param]) == 'undefined' || Account[type][param] == "") {
- if (!params[i].optional) {
- self.props.flux.actions.global.showNotification("Please fill all account details");
- return;
- }
- }
- }
- }
-
- let nestedParams = Account.nestedParams && Account.nestedParams;
- if (nestedParams && nestedParams.params) {
- for (let i = 0; i < nestedParams.params.length; i++) {
- let nestedParam = nestedParams.params[i].ref;
- if (typeof(Account[type]) == 'undefined' || typeof(Account[type][nestedParams['container-name']][nestedParam]) == 'undefined' || Account[type][nestedParams['container-name']][nestedParam] == "") {
- if (!nestedParams.params[i].optional) {
- self.props.flux.actions.global.showNotification("Please fill all account details");
- return;
- }
- }
- }
+ if(!wasAllDetailsFilled(Account)) {
+ self.props.flux.actions.global.showNotification("Please fill all account details");
+ return;
}
}
let newAccount = _cloneDeep(removeTrailingWhitespace(Account));
+
delete newAccount.params;
+
newAccount.nestedParams &&
- newAccount.nestedParams['container-name'] &&
- delete newAccount[newAccount.nestedParams['container-name']];
+ newAccount.nestedParams['container-name'] &&
+ delete newAccount[newAccount.nestedParams['container-name']];
+
delete newAccount.nestedParams;
+ if(AccountType == 'resource-orchestrator') {
+ newAccount['ro-account-type'] = newAccount['account-type'] || newAccount['ro-account-type'];
+ delete newAccount['account-type'];
+ }
+ if(AccountType == 'cloud' && self.props.vduInstanceTimeout != '') {
+ newAccount['vdu-instance-timeout'] = self.props.vduInstanceTimeout;
+ }
this.props.flux.actions.global.showScreenLoader();
- this.props.store.create(newAccount, AccountType).then(function() {
- self.props.router.push({pathname:'accounts'});
- self.props.flux.actions.global.hideScreenLoader.defer();
- },
- function(error) {
- self.props.flux.actions.global.showNotification(Utils.parseError(error));
- self.props.flux.actions.global.hideScreenLoader.defer();
- });
+ this.props.store.create(newAccount, AccountType).then(
+ function() {
+ self.props.router.push({pathname:'accounts'});
+ self.props.flux.actions.global.hideScreenLoader.defer();
+ },
+ function(error) {
+ self.props.flux.actions.global.showNotification(error);
+ self.props.flux.actions.global.hideScreenLoader.defer();
+ }
+ );
}
update(e) {
e.preventDefault();
var self = this;
var Account = this.state.account;
let AccountType = this.state.accountType;
- this.props.flux.actions.global.showScreenLoader();
- this.props.store.update(Account, AccountType).then(function() {
- self.props.router.push({pathname:'accounts'});
- self.props.flux.actions.global.hideScreenLoader();
- },
- function() {
- });
+ if(!wasAllDetailsFilled(Account)) {
+ self.props.flux.actions.global.showNotification("Please fill all account details");
+ return;
+ }
+
+ if(AccountType == 'cloud' && self.props.vduInstanceTimeout != '') {
+ Account['vdu-instance-timeout'] = self.props.vduInstanceTimeout;
+ }
+ this.props.flux.actions.global.showScreenLoader();
+ this.props.store.update(Account, AccountType).then(
+ function() {
+ self.props.router.push({pathname:'accounts'});
+ self.props.flux.actions.global.hideScreenLoader();
+ },
+ function(error){
+ let msg = isAccessDenied(error) ?
+ "Update of account failed. No authorization to modify accounts."
+ : "Update of account failed. This could be because the account is in use or no longer exists.";
+ self.props.flux.actions.global.hideScreenLoader.defer();
+ self.props.flux.actions.global.showNotification.defer(msg);
+ }
+ );
}
cancel = (e) => {
e.preventDefault();
e.stopPropagation();
+ this.props.flux.actions.global.handleCancelAccount();
this.props.router.push({pathname:'accounts'});
}
handleDelete = () => {
let msg = 'Preparing to delete "' + self.state.account.name + '"' +
' Are you sure you want to delete this ' + self.state.accountType + ' account?"';
if (window.confirm(msg)) {
- this.props.store.delete(self.state.accountType, self.state.account.name).then(function() {
- self.props.flux.actions.global.hideScreenLoader();
- self.props.router.push({pathname:'accounts'});
- }, function(){
- // self.props.flux.actions.global.hideScreenLoader.defer();
- // console.log('Delete Account Fail');
- });
- } else {
- self.props.flux.actions.global.hideScreenLoader();
+ this.props.flux.actions.global.showScreenLoader();
+ this.props.store.delete(self.state.accountType, self.state.account.name).then(
+ function() {
+ self.props.flux.actions.global.hideScreenLoader();
+ self.props.router.push({pathname:'accounts'});
+ },
+ function(error){
+ let msg = isAccessDenied(error) ?
+ "Deletion of account failed. No authorization to delete accounts."
+ : "Deletion of account failed. This could be because the account is in use or has already been deleted.";
+ self.props.flux.actions.global.hideScreenLoader.defer();
+ self.props.flux.actions.global.showNotification.defer(msg);
+ }
+ );
}
}
handleNameChange(event) {
this.props.store.handleNameChange(event);
}
- handleAccountTypeChange(node, event) {
- this.props.store.handleAccountTypeChange(node, event);
+ handleAccountTypeChange(node, isRo, event) {
+ this.props.store.handleAccountTypeChange(node, isRo, event);
}
handleSelectSdnAccount = (e) => {
var tmp = this.state.account;
}
console.log(e, tmp)
}
+ updateVduInstanceTimeout(event) {
+ this.props.store.updateVduTimeout(event)
+ }
preventDefault = (e) => {
e.preventDefault();
e.stopPropagation();
let {store, ...props} = this.props;
// This section builds elements that only show up on the create page.
// var name = <label>Name <input type="text" onChange={this.handleNameChange.bind(this)} style={{'textAlign':'left'}} /></label>;
- var name = <TextInput label="Name" onChange={this.handleNameChange.bind(this)} required={true} />;
+ let Account = this.state.account || {};
+ var name = <TextInput label="Name" onChange={this.handleNameChange.bind(this)} required={true} value={Account &&Account.name || ''} />;
let params = null;
let selectAccount = null;
let resfreshStatus = null;
- let Account = this.state.account;
// AccountType is for the view, not the data account-type value;
let AccountType = this.state.accountType;
let Types = this.state.types;
var buttons;
let cloudResources = Account['cloud-resources-state'] && Account['cloud-resources-state'][Account['account-type']];
let cloudResourcesStateHTML = null;
-
// Account Type Radio
var selectAccountStack = [];
+ let setVduTimeout = null;
if (!isEdit) {
buttons = [
<Button key="0" onClick={this.cancel} className="cancel light" label="Cancel"></Button>,
]
for (var i = 0; i < Types.length; i++) {
var node = Types[i];
- var isSelected = (Account['account-type'] == node['account-type']);
+ const isRo = node.hasOwnProperty('ro-account-type');
+ var isSelected = (Account['account-type'] || Account['ro-account-type']) == (node['account-type'] || node['ro-account-type']);
selectAccountStack.push(
<label key={i} className={"accountSelection " + (isSelected ? "accountSelection--isSelected" : "")}>
<div className={"accountSelection-overlay" + (isSelected ? "accountSelection-overlay--isSelected" : "")}></div>
<div className="accountSelection-imageWrapper">
- <img src={store.getImage(node['account-type'])}/>
+ <img src={store.getImage(node['account-type'] || node['ro-account-type'])}/>
</div>
- <input type="radio" name="account" onChange={this.handleAccountTypeChange.bind(this, node)} defaultChecked={node.name == Types[0].name} value={node['account-type']} />{node.name}
+ <input type="radio" name="account" onChange={this.handleAccountTypeChange.bind(this, node, isRo)} defaultChecked={node.name == Types[0].name} value={node['account-type'] || node['ro-account-type']} />{node.name}
</label>
)
}
}
}
//END Cloud Account: SDN Account Option
+
+ setVduTimeout = <TextInput
+ className="accountForm-input"
+ label="VIM Instantiation Timeout (Seconds)"
+ onChange={this.updateVduInstanceTimeout.bind(this)}
+ value={this.props.vduInstanceTimeout}
+ readonly={self.props.readonly}
+ placeholder={300}
+ />
+
//
// This sections builds the parameters for the account details.
if (Account.params) {
var paramsStack = [];
var optionalField = '';
+ var isRo = Account.hasOwnProperty('ro-account-type');
for (var i = 0; i < Account.params.length; i++) {
var node = Account.params[i];
var value = ""
- if (Account[Account['account-type']]) {
- value = Account[Account['account-type']][node.ref]
+ if(isRo) {
+ if (Account[Account['ro-account-type']] && Account[Account['ro-account-type']][node.ref]) {
+ value = Account[Account['ro-account-type']][node.ref]
+ }
+ } else {
+ if (Account[Account['account-type']] && Account[Account['account-type']][node.ref]) {
+ value = Account[Account['account-type']][node.ref]
+ }
}
if (this.props.edit && Account.params) {
value = Account.params[node.ref];
}
- paramsStack.push(
- <TextInput key={node.label} className="accountForm-input" label={node.label} required={!node.optional} onChange={this.props.store.handleParamChange(node)} value={value} />
- );
+ if(node.ref == "password" || node.ref == "secret") {
+ let displayValue = value;
+ if (self.props.readonly) {
+ displayValue = new Array(value.length + 1).join( '*' );
+ }
+ paramsStack.push(
+ <TextInput
+ key={node.label}
+ className="accountForm-input"
+ label={node.label}
+ required={!node.optional}
+ onChange={this.props.store.handleParamChange(node, isRo)}
+ value={displayValue}
+ readonly={self.props.readonly}
+ type="password"
+ />
+ );
+ } else {
+ paramsStack.push(
+ <TextInput
+ key={node.label}
+ className="accountForm-input"
+ label={node.label}
+ required={!node.optional}
+ onChange={this.props.store.handleParamChange(node, isRo)}
+ value={value}
+ readonly={self.props.readonly}
+ />
+ );
+ }
}
let nestedParamsStack = null;
// <input className="create-fleet-pool-input" type="text" onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)} value={value}/>
// </label>
// );
- nestedParamsStack.push(
- <TextInput key={node.label} label={node.label} required={!node.optional} className="create-fleet-pool-input" type="text" onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)} value={value}/>
- );
+ if(node.ref == "password" || node.ref == "secret") {
+ let displayValue = value;
+ if (self.props.readonly) {
+ displayValue = new Array(value.length + 1).join( '*' );
+ }
+ nestedParamsStack.push(
+ <TextInput
+ key={node.label}
+ label={node.label}
+ required={!node.optional}
+ className="create-fleet-pool-input"
+ type="password"
+ onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)}
+ value={displayValue}
+ readonly={self.props.readonly}
+ />
+ );
+ } else {
+ nestedParamsStack.push(
+ <TextInput
+ key={node.label}
+ label={node.label}
+ required={!node.optional}
+ className="create-fleet-pool-input"
+ type="text"
+ onChange={this.props.store.handleNestedParamChange(Account.nestedParams['container-name'], node)}
+ value={value}
+ readonly={self.props.readonly}
+ />
+ );
+ }
}
}
<AccountConnectivityStatus status={Account['connection-status'].status} />
{Account['connection-status'] && Account['connection-status'].status && Account['connection-status'].status.toUpperCase()}
</div>
- <Button className="refreshList light" onClick={this.props.store.refreshAccount.bind(this, Account.name, AccountType)} label="REFRESH STATUS"></Button>
+ { self.props.readonly ? null :
+ <Button is-disabled={self.props.readonly} className="refreshList light" onClick={this.props.store.refreshAccount.bind(this, Account.name, AccountType)} label="REFRESH STATUS" />
+ }
</div>
{
(Account['connection-status'] && Account['connection-status'].status && Account['connection-status'].status.toUpperCase()) === 'FAILURE' ?
}
</div>
) : null;
- // cloudResourcesStateHTML = (
- // <div className="accountForm">
- // <h3 className="accountForm-title">Resources Status</h3>
- // <div className="accountForm-content" >
- // <ul>
- // {
- // cloudResources && props.AccountMeta.resources[Account['account-type']].map(function(r, i) {
-
- // return (
- // <li key={i}>
- // {r}: {cloudResources[r]}
- // </li>
- // )
- // }) || 'No Additional Resources'
- // }
- // </ul>
- // </div>
- // </div>
- // )
+
}
var html = (
<div className="associateSdnAccount accountForm">
<h3 className="accountForm-title">Account</h3>
<div className="accountForm-content" style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
- <h4 style={{flex: '1'}}>{name}</h4>
+ {name}
{ isEdit ?
(
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
- <img src={store.getImage(Account['account-type'])}/> {props.AccountMeta.labelByType[Account['account-type']]}
+ <img src={store.getImage(Account['account-type'] || Account['ro-account-type'])}/> {props.AccountMeta.labelByType[Account['account-type'] || Account['ro-account-type']]}
</div>)
: null
}
</div>
+ {
+ AccountType == 'cloud' ? (
+ <div className="accountForm-content">
+ {setVduTimeout}
+ </div>
+ )
+ : null
+ }
</div>
{selectAccount}
{params}
</ol>
<div className="form-actions">
- {buttons}
+ {!self.props.readonly ? buttons : null}
</div>
</form>
)
- return html;
+ return Types.length ? html : null;
}
}
+Account.contextTypes = {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+}
+
function displayFailureMessage(msg) {
return (
<div className="accountForm-content" style={{maxWidth: '600px'}}>
}
}
+function wasAllDetailsFilled(Account) {
+ var type = Account['account-type'] || Account['ro-account-type'];
+ var params = Account.params;
+
+ if(params) {
+ for (var i = 0; i < params.length; i++) {
+ var param = params[i].ref;
+ if (typeof(Account[type]) == 'undefined' || typeof(Account[type][param]) == 'undefined' || Account[type][param] == "") {
+ if (!params[i].optional) {
+ return false;
+ }
+ }
+ }
+ }
+
+ let nestedParams = Account.nestedParams && Account.nestedParams;
+ if (nestedParams && nestedParams.params) {
+ for (let i = 0; i < nestedParams.params.length; i++) {
+ let nestedParam = nestedParams.params[i].ref;
+ if (typeof(Account[type]) == 'undefined' || typeof(Account[type][nestedParams['container-name']][nestedParam]) == 'undefined' || Account[type][nestedParams['container-name']][nestedParam] == "") {
+ if (!nestedParams.params[i].optional) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
function removeTrailingWhitespace(Account) {
- var type = Account['account-type'];
+ var type = Account['account-type'] || Account['ro-account-type'];
var params = Account.params;
if(params) {
'deleteAccountLoading',
'deleteAccountFail',
'viewAccount',
- 'getResourceOrchestratorSuccess'
+ 'getResourceOrchestratorSuccess',
+ 'handleCancelAccount'
);
}
return resolve(false);
}
$.ajax({
- url: '/socket-polling?api_server=' + API_SERVER,
+ url: '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
}
function refreshStatus(type, name) {
- let url = type + '/' + name + '/refresh?api_server=' + API_SERVER;
+ let url = type + '/' + encodeURIComponent(name) + '/refresh?api_server=' + API_SERVER;
setTimeout(function(){
Refreshing.next();
},100);
remote: function(state, name, type) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: type + '/' + name + '/refresh?api_server=' + API_SERVER,
+ url: type + '/' + encodeURIComponent(name) + '/refresh?api_server=' + API_SERVER,
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
success: function(account) {
create: {
remote: function(state, account, type, cb) {
delete account['connection-status'];
+ var payload = account;
+ var accountKey = payload.hasOwnProperty('account-type') ? 'account-type' : 'ro-account-type';
+ var payloadKeys = Object.keys(payload[payload[accountKey]]);
+ var accountData = payload[payload[accountKey]];
+ payloadKeys.map(function(k) {
+ if (!accountData[k] || accountData[k].trim() == '') {
+ delete payload[payload[accountKey]][k];
+ }
+ });
return new Promise(function(resolve, reject) {
$.ajax({
url: type + '?api_server=' + API_SERVER,
type:'POST',
beforeSend: Utils.addAuthorizationStub,
- data: JSON.stringify(account),
+ data: JSON.stringify(payload),
contentType: "application/json",
success: function(data) {
resolve({data, cb});
update: {
remote: function(state, account, type, cb) {
var payload = Object.assign({}, account);
+ delete payload["instance-ref-count"]
delete payload['connection-status'];
delete payload['params'];
delete payload['pools'];
+ delete payload['datacenters'];
+ delete payload['config-data'];
(
(payload.nestedParams == null) &&
delete payload.nestedParams
delete payload.nestedParams
);
+ var payloadKeys = Object.keys(payload[payload['account-type']]);
+ var accountData = payload[payload['account-type']];
+ payloadKeys.map(function(k) {
+ if (!accountData[k] || accountData[k].trim() == '') {
+ delete payload[payload['account-type']][k];
+ }
+ });
return new Promise(function(resolve, reject) {
$.ajax({
- url: type + '/' + account.name + '?api_server=' + API_SERVER,
+ url: type + '/' + encodeURIComponent(account.name) + '?api_server=' + API_SERVER,
type:'PUT',
beforeSend: Utils.addAuthorizationStub,
data: JSON.stringify(payload),
},
error: function(error) {
console.log("There was an error updating the account: ", arguments);
-
}
}).fail(function(xhr){
//Authentication and the handling of fail states should be wrapped up into a connection class.
}),
success: Alt.actions.global.createAccountSuccess,
loading: Alt.actions.global.createAccountLoading,
- error: Alt.actions.global.showNotification
+ error: Alt.actions.global.createAccountFail
},
delete: {
remote: function(state, type, name, cb) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: type + '/' + name + '/?api_server=' + API_SERVER,
+ url: type + '/' + encodeURIComponent(name) + '/?api_server=' + API_SERVER,
type:'DELETE',
dataType : 'html',
beforeSend: Utils.addAuthorizationStub,
'error': 'Something went wrong while trying to delete the account. Check the error logs for more information' }),
success: Alt.actions.global.deleteAccountSuccess,
loading: Alt.actions.global.deleteAccountLoading,
- error: Alt.actions.global.showNotification
- },
- getResourceOrchestrator: {
- remote: function() {
- return new Promise(function(resolve, reject) {
- $.ajax({
- url: 'passthrough/data/api/running/resource-orchestrator' + '?api_server=' + API_SERVER,
- type: 'GET',
- beforeSend: Utils.addAuthorizationStub,
- contentType: "application/json",
- success: function(data) {
- let returnedData;
- if (data.hasOwnProperty("rw-launchpad:resource-orchestrator")) {
- returnedData = data;
- } else {
- returnedData = {};
- }
- resolve(returnedData);
- },
- error: function(error) {
- console.log("There was an error updating the account: ", arguments);
-
- }
- }).fail(function(xhr){
- //Authentication and the handling of fail states should be wrapped up into a connection class.
- Utils.checkAuthentication(xhr.status);
- return reject('error');
- });
- });
- },
- interceptResponse: interceptResponse({
- 'error': 'There was an error retrieving the resource orchestrator information.'
- }),
- success: Alt.actions.global.getResourceOrchestratorSuccess,
- loading: Alt.actions.global.showScreenLoader,
- error: Alt.actions.global.showNotification
- },
+ error: Alt.actions.global.deleteAccountFail
+ }
}
}
var Utils = require('utils/utils.js');
var rw = require('utils/rw.js');
var altImage = rw.getSearchParams(window.location).alt_image;
+var _ = require('lodash');
let Params = {
//Config Agent
}
-
let AccountMeta = {
+ 'resource-orchestrator': {
+ defaultType: 'openmano',
+ types: [
+ {
+ name: "OpenMANO",
+ 'ro-account-type': 'openmano'
+ }],
+ params: {
+ 'openmano' : [{
+ label: "Host",
+ ref: 'host'
+ }, {
+ label: "Port",
+ ref: 'port'
+ }, {
+ label: "Tenant ID",
+ ref: 'tenant-id'
+ }]
+ }
+ },
'config-agent': {
defaultType: 'juju',
}, {
label: "URL",
ref: 'url'
- }]
+ }],
+
+ "openstack": [{
+ label: "Key",
+ ref: 'key'
+ },{
+ label: "Secret",
+ ref: 'secret'
+ },{
+ label: "Authentication URL",
+ ref: 'auth_url'
+ },{
+ label: "Tenant",
+ ref: 'tenant'
+ },{
+ label: "User domain",
+ ref: 'user-domain',
+ optional: true
+ },{
+ label: "Project domain",
+ ref: 'project-domain',
+ optional: true
+ },{
+ label: "Region",
+ ref: 'region',
+ optional: true
+ }
+ // ,{
+ // label: "admin",
+ // ref: 'admin',
+ // default: false,
+ // optional: true
+ // }
+ // ,{
+ // label: "Management Network",
+ // ref: 'mgmt-network'
+ // }
+ // ,{
+ // label: "Plugin Name",
+ // ref: 'plugin-name',
+ // optional: true
+ // },{
+ // label: "Security Groups",
+ // ref: 'security-groups',
+ // type: 'list',
+ // optional: true
+ // },{
+ // label: "Dynamic Flavor Support ",
+ // ref: 'dynamic-flavor-support',
+ // type: 'boolean',
+ // optional: true
+ // }
+ //, {
+ // label: "Floating IP Pool",
+ // ref: 'floating-ip-pool',
+ // optional: true
+ // }
+ // ,{
+ // label: "Certificate Validation",
+ // ref: 'cert-validate',
+ // type: 'boolean',
+ // optional: true
+ // }
+ ]
+
},
types: [{
"name": "ODL",
"account-type": "odl",
+ },{
+ "name": "OpenStack",
+ "account-type": "openstack",
}]
},
'cloud': {
label: "Tenant",
ref: 'tenant'
}, {
- label: 'Management Network',
- ref: 'mgmt-network'
+ label: 'Default Management Network',
+ ref: 'mgmt-network',
+ optional: true
}, {
- label: 'Floating IP Pool Network Name',
+ label: 'Default Floating IP Pool Network Name',
ref: 'floating-ip-pool',
optional: true
}, {
label: "Port",
ref: 'port',
optional: true
- }]
+ }],
+ "prop_cloud1": [{
+ label: "Host",
+ ref: "host"
+ }, {
+ label: "Username",
+ ref: "username"
+ }, {
+ label: "Password",
+ ref: "password"
+ }, {
+ label: "Management Network",
+ ref: "mgmt-network"
+ }, {
+ label: "Public IP pool",
+ ref: "public-ip-pool"
+ }, {
+ label: "WAN Interface",
+ ref: "wan-interface"
+ }, {
+ label: "Firewall",
+ ref: "firewall",
+ optional: true
+ }]
},
nestedParams: {
"openvim": {
}, {
"name": "Open VIM",
"account-type": "openvim"
- }]
+ }, {
+ "name": "Brocade",
+ "account-type": "prop_cloud1"
+ }]
},
resources: {
},
"openstack": require("../../images/openstack.png"),
"cloudsim_proxy": require("../../images/riftio.png"),
"odl": require("../../images/OpenDaylight_logo.png"),
- "juju": require("../../images/juju.svg")
+ "juju": require("../../images/juju.svg"),
+ "prop_cloud1": require("../../images/brocade.png"),
+ "openmano": require("../../images/openmano.png")
},
labelByType: {
"aws": "AWS",
"openvim": "Open VIM",
"openstack": "OpenStack",
- "cloudsim_proxy": "Cloudsim"
+ "cloudsim_proxy": "Cloudsim",
+ "prop_cloud1": "Brocade",
+ "openmano": "OpenStack"
}
}
export default class AccountStore {
constructor() {
+ // const savedData = JSON.parse(window.sessionStorage.getItem('account'));
+ const savedData = null;
+ this.saveAccountToSessionStorage(null, true)
this.cloud = [];
this['config-agent'] = [];
+ this['resource-orchestrator'] = [];
this.sdn = [];
+ this.savedData = savedData;
this.account = null;
- this.types = [];
+ // this.account = savedData.account;
+ // this.accountType = savedData.accountType;
+ this.types = this.accountType ? AccountMeta[this.accountType].types : [];
this.refreshingAll = false;
this.sdnOptions = [];
this.AccountMeta = AccountMeta;
this.showVIM = true;
+ this.vduInstanceTimeout = '';
this.bindActions(AccountActions(this.alt));
this.registerAsync(AccountSource);
this.exportPublicMethods({
updateAccount: this.updateAccount,
viewAccount: this.viewAccount,
handleNestedParamChange: this.handleNestedParamChange,
- getImage: this.getImage
+ getImage: this.getImage,
+ saveAccountToSessionStorage: this.saveAccountToSessionStorage,
+ updateVduTimeout: this.updateVduTimeout,
+ getTransientAccountForUser: this.getTransientAccountForUser
})
}
refreshAllAccountsSuccess = () => {
}
refreshCloudAccountSuccess = () => {
- }
- getResourceOrchestratorSuccess = (data) => {
- this.alt.actions.global.hideScreenLoader.defer();
- if(data['rw-launchpad:resource-orchestrator'] && (data['rw-launchpad:resource-orchestrator']['account-type'] == 'openmano')) {
- this.setState({
- showVIM: false
- })
- }
}
deleteAccountSuccess = (response) => {
+ this.saveAccountToSessionStorage(null, true);
this.setState({
currentAccount: false,
account: {}
cloud: data.cloud.data,
'config-agent': data['config-agent'].data,
sdn: data.sdn.data,
+ 'resource-orchestrator': data['resource-orchestrator'].data,
sdnOptions: SdnOptions
};
//If account is selected, updated connection status only
- if(self.currentAccount) {
+ if(self.currentAccount && (self.account || self.savedData)) {
let Account = self.getAccountFromStream(data[self.currentAccount.type].data, self.currentAccount.name);
- newState.account = self.account;
- newState.account['connection-status'] = Account['connection-status']
+ newState.account = self.changedData ? self.account : self.savedData || self.account;
+
+ newState.account['connection-status'] = Account && Account['connection-status'];
+ newState.savedData = null;
}
- self.setState(newState)
+ self.setState(newState);
} catch(error) {
console.log('Hit at exception in openAccountSocketSuccess', error)
}
socket: null
})
}
- setAccountTemplate = (AccountType, type) => {
+ setAccountTemplate = (AccountType, type, savedData) => {
+ console.log('Setting Account Template')
let account = {
name: '',
'account-type': type || AccountMeta[AccountType].defaultType,
account[type || AccountMeta[AccountType].defaultType] = {}
this.setState({
- account: account,
+ account: savedData || account,
accountType: AccountType,
types: AccountMeta[AccountType].types,
- currentAccount: null
+ currentAccount: null,
+ changedData: true,
+ savedData: null
})
}
getAccountFromStream(data, name) {
let result = null;
- data.map(function(a) {
+ data && _.isArray(data) && data.map(function(a) {
if(a.name == name) {
result = a;
}
});
return result;
}
- viewAccount = ({type, name}) => {
+ viewAccount = ({type, name}, savedData) => {
+ console.log('Viewing account')
var data = null;
var accounts = null;
- if(this && this[type].length) {
+ let vduInstanceTimeout = '';
+ if(this && this[type] && this[type].length) {
accounts = this[type];
data = this.getAccountFromStream(accounts, name);
+ const isRo = data.hasOwnProperty('ro-account-type');
if(data) {
let accountParams = {
- params: AccountMeta[type].params[data['account-type']]
+ params: AccountMeta[type].params[
+ isRo ? data['ro-account-type'] : data['account-type']
+ ]
};
let accountNestedParams = {
- nestedParams: AccountMeta[type].nestedParams?AccountMeta[type].nestedParams[data['account-type']]:null
+ nestedParams: AccountMeta[type].nestedParams?AccountMeta[type].nestedParams[
+ isRo ? data['ro-account-type'] : data['account-type']
+ ]:null
};
-
+ if (data.hasOwnProperty('vdu-instance-timeout')) {
+ vduInstanceTimeout = data['vdu-instance-timeout'];
+ }
this.setState({
currentAccount: {type, name},
- account: Object.assign(data, accountParams, accountNestedParams),
- accountType: type
+ account: (savedData ? savedData : Object.assign(data, accountParams, accountNestedParams)) || null,
+ accountType: type,
+ types: AccountMeta[type].types,
+ vduInstanceTimeout: vduInstanceTimeout,
+ savedData: null
})
}
+ } else {
+ this.setState({
+ currentAccount: {type, name},
+ account: null,
+ accountType: type,
+ types: AccountMeta[type].types,
+ })
}
}
generateOptionsByName(data) {
let results = [];
- if (data && data.constructor.name == "Array") {
+ if (data && _.isArray(data)) {
data.map(function(d) {
results.push({
label: d.name,
return results;
}
updateAccount = (account) => {
- this.setState({account:account})
+ this.saveAccountToSessionStorage(account);
+ this.setState({account:account,
+ changedData: true})
}
handleNameChange = (event) => {
var account = this.account;
account.name = event.target.value;
+ this.saveAccountToSessionStorage(account)
this.setState(
{
- account:account
+ account:account,
+ changedData: true
}
);
}
- handleAccountTypeChange = (node, event) => {
+ updateVduTimeout = (event) => {
+ var vduInstanceTimeout = event.target.value;
+ this.setState(
+ {
+ vduInstanceTimeout:vduInstanceTimeout
+ }
+ );
+ }
+ handleAccountTypeChange = (node, isRo, event) => {
var temp = {};
temp.name = this.account.name;
- temp['account-type'] = event.target.value;
+ if (isRo) {
+ temp['ro-account-type'] = event.target.value;
+ } else {
+ temp['account-type'] = event.target.value;
+ }
temp.params= AccountMeta[this.accountType].params[event.target.value];
- temp.nestedParams = AccountMeta[this.accountType]?AccountMeta[this.accountType].nestedParams[event.target.value]:null;
+ temp.nestedParams = (AccountMeta[this.accountType] && AccountMeta[this.accountType].nestedParams )?AccountMeta[this.accountType].nestedParams[event.target.value]:null;
temp[event.target.value] = {};
+ this.saveAccountToSessionStorage(temp)
this.setState({
- account: temp
+ account: temp,
+ changedData: true
});
}
- handleParamChange(node, event) {
+ handleParamChange(node, isRo, event) {
return function(event) {
var account = this.state.account;
- account[account['account-type']][node.ref] = event.target.value;
+ if (isRo) {
+ account[account['ro-account-type']][node.ref] = event.target.value;
+
+ } else {
+ account[account['account-type']][node.ref] = event.target.value;
+
+ }
account.params[node.ref] = event.target.value;
this.updateAccount(account);
}.bind(this);
getImage = (type) => {
return AccountMeta.image[type];
}
+ createAccountSuccess = () => {
+ this.setState({account: {}})
+ this.saveAccountToSessionStorage(null, true)
+ }
+ handleCancelAccount = () => {
+ this.setState({account: {}, currentAccount: null, savedData: null, accountType: null, types: []})
+ this.saveAccountToSessionStorage(null, true)
+ }
+ saveAccountToSessionStorage = (account, clear) => {
+ const userProfile = this.userProfile;
+ if(userProfile) {
+ if(clear) {
+
+ window.sessionStorage.removeItem(userProfile.userId + '@' + userProfile.domain + ':account');
+ // this.setState({
+ // account:null,
+ // accountType: null
+ // })
+ } else {
+ const state = account || this.account;
+ window.sessionStorage.setItem(userProfile.userId + '@' + userProfile.domain + ':account', JSON.stringify(state));
+ }
+ }
+
+ }
+ getTransientAccountForUser = (userProfile) => {
+ let userProfileTransientAccount = window.sessionStorage.getItem(userProfile.userId + '@' + userProfile.domain + ':account') || null;
+ var transientAccount = null;
+ if (userProfileTransientAccount) {
+ transientAccount = JSON.parse(userProfileTransientAccount);
+ };
+ this.saveAccountToSessionStorage(null, true);
+ if(!this.userProfile) {
+ this.setState({
+ savedData: transientAccount,
+ userProfile: userProfile
+ })
+ }
+ }
}
import AccountStore from './accountStore.js';
import AccountSidebar from '../account_sidebar/accountSidebar.jsx';
import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
+//Delete this line after testing is done
+// PROJECT_ROLES.ACCOUNT_ADMIN = '';
import 'style/layout.scss';
class AccountsDashboard extends React.Component {
componentWillMount() {
this.Store.listen(this.updateState);
this.Store.openAccountsSocket();
- this.Store.getResourceOrchestrator();
}
componentWillUnmount() {
this.Store.closeSocket();
render() {
let self = this;
let html;
+ let READONLY = !isRBACValid(this.context.userProfile, [PROJECT_ROLES.ACCOUNT_ADMIN, PROJECT_ROLES.PROJECT_ADMIN]);
html = (<div className="launchpad-account-dashboard content-wrapper">
<div className="flex">
- <AccountSidebar {...this.state} store={this.Store}/>
+ <AccountSidebar {...this.state} readonly={READONLY} store={this.Store}/>
<div>
- { this.props.children ? React.cloneElement(this.props.children, {store: self.Store, ...self.state}) : 'Edit or Create New Accounts'
+ { this.props.children ? React.cloneElement(this.props.children, {readonly: READONLY, store: self.Store, ...self.state}) : 'Edit or Create New Accounts'
}
</div>
</div>
}
}
AccountsDashboard.contextTypes = {
- router: React.PropTypes.object
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
};
export default SkyquakeComponent(AccountsDashboard);
}
render() {
let html;
+ let self = this;
let {store, ...props} = this.props;
//[this.props.cloud,this.props.sdn,this.props['config-agent']]
let AccountData = [
{
type: 'config',
data: this.props['config-agent']
+ },
+ {
+ type: 'resource-orchestrator',
+ data: this.props['resource-orchestrator']
}
];
let refreshStatus = (<div>Check All Connectivity Status</div>)
+ let resourceOrchestrators = (this.props['resource-orchestrator'].length > 0) ? this.props['resource-orchestrator'].map(function(orchestrator, index) {
+ let status = null;
+ if (orchestrator) {
+ if (orchestrator['connection-status']) {
+ status = orchestrator['connection-status'].status;
+ }
+ return (
+ <DashboardCard key={index} className='pool-card accountSidebarCard'>
+ <header>
+ <Link to={'accounts/resource-orchestrator/' + encodeURIComponent(orchestrator.name)}>
+ <div className="accountSidebarCard--content">
+ <img className="accountSidebarCard--logo" src={store.getImage(orchestrator['ro-account-type'])} />
+ <h3 title="Edit Resource Orchestrator(RO) Account">
+ <span className="accountSidebarCard--name" title={orchestrator.name}>{orchestrator.name}</span>
+ <AccountConnectivityStatus status={status}/>
+ </h3>
+ </div>
+ </Link>
+ </header>
+ </DashboardCard>
+ );
+ }
+ }) : null;
let cloudAccounts = (this.props.cloud.length > 0) ? this.props.cloud.map(function(account, index) {
let status = null;
if (account) {
return (
<DashboardCard key={index} className='pool-card accountSidebarCard'>
<header>
- <Link to={'accounts/cloud/' + account.name}>
+ <Link to={'accounts/cloud/' + encodeURIComponent(account.name)} onClick={self.props.actions.handleCancelAccount}>
<div className="accountSidebarCard--content">
<img className="accountSidebarCard--logo" src={store.getImage(account['account-type'])} />
<h3 title="Edit Account">
- {account.name}
+ <span className="accountSidebarCard--name" title={account.name}>{account.name}</span>
<AccountConnectivityStatus status={status}/>
</h3>
</div>
return (
<DashboardCard key={index} className='pool-card accountSidebarCard'>
<header>
- <Link to={'accounts/sdn/' + account.name} title="Edit Account">
+ <Link to={'accounts/sdn/' + encodeURIComponent(account.name)} title="Edit Account">
<div className="accountSidebarCard--content">
<img className="accountSidebarCard--logo" src={store.getImage(account['account-type'])} />
- <h3>{account.name}<AccountConnectivityStatus status={status}/></h3>
+ <h3><span className="accountSidebarCard--name" title={account.name}>{account.name}</span><AccountConnectivityStatus status={status}/></h3>
</div>
</Link>
</header>
return (
<DashboardCard key={index} className='pool-card accountSidebarCard'>
<header>
- <Link to={'accounts/config-agent/' + account.name} title="Edit Account">
+ <Link to={'accounts/config-agent/' + encodeURIComponent(account.name)} title="Edit Account">
<div className="accountSidebarCard--content">
<img className="accountSidebarCard--logo" src={store.getImage(account['account-type'])} />
<h3 title="Edit Account">
- {account.name}
+ <span className="accountSidebarCard--name" title={account.name}>{account.name}</span>
<AccountConnectivityStatus status={status}/>
</h3>
</div>
}) : null;
html = (
<div className='accountSidebar'>
- <Button className="refreshList light" onClick={this.props.store.refreshAll.bind(this, AccountData)} label={this.props.refreshingAll ? 'Checking Connectivity Status...' : refreshStatus}></Button>
+ {
+ self.props.readonly ? null :
+ <Button className="refreshList light" onClick={this.props.store.refreshAll.bind(this, AccountData)} label={this.props.refreshingAll ? 'Checking Connectivity Status...' : refreshStatus}/>
+ }
+ <div>
+ <h1>RO Accounts</h1>
+ {resourceOrchestrators}
+ {
+ !self.props.readonly ?
+ <DashboardCard className="accountSidebarCard">
+ <Link
+ to={{pathname: '/accounts/resource-orchestrator/create'}}
+ title="Create Resource Orchestrator(RO) Account"
+ className={'accountSidebarCard_create'}
+ onClick={self.props.actions.handleCancelAccount} >
+ Add RO Account
+ <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
+ </Link>
+ </DashboardCard>
+ : <div style={{margin:'1rem'}}></div>
+ }
+ </div>
{props.showVIM ? (
<div>
<h1>VIM Accounts</h1>
{cloudAccounts}
- <DashboardCard className="accountSidebarCard">
- <Link
- to={{pathname: '/accounts/cloud/create'}}
- title="Create Cloud Account"
- className={'accountSidebarCard_create'}
- >
- Add VIM Account
- <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
- </Link>
- </DashboardCard>
+ {
+ !self.props.readonly ?
+ <DashboardCard className="accountSidebarCard">
+ <Link
+ to={{pathname: '/accounts/cloud/create'}}
+ title="Create Cloud Account"
+ className={'accountSidebarCard_create'}
+ onClick={self.props.actions.handleCancelAccount} >
+ Add VIM Account
+ <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
+ </Link>
+ </DashboardCard>
+ : <div style={{margin:'1rem'}}></div>
+ }
</div>)
: null}
<h1>SDN Accounts</h1>
{sdnAccounts}
- <DashboardCard className="accountSidebarCard">
- <Link
- to={{pathname: '/accounts/sdn/create'}}
- title="Create Sdn Account"
- className={'accountSidebarCard_create'}
- >
- Add SDN Account
- <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
- </Link>
- </DashboardCard>
+ {
+ !self.props.readonly ?
+ <DashboardCard className="accountSidebarCard">
+ <Link
+ to={{pathname: '/accounts/sdn/create'}}
+ title="Create Sdn Account"
+ className={'accountSidebarCard_create'}
+ onClick={self.props.actions.handleCancelAccount}>
+ Add SDN Account
+ <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
+ </Link>
+
+ </DashboardCard>
+ : <div style={{margin:'1rem'}}></div>
+ }
<h1>Config Agent Accounts</h1>
{configAgentAccounts}
- <DashboardCard className="accountSidebarCard">
- <Link
- to={{pathname: '/accounts/config-agent/create'}}
- title="Create Config Agent Account"
- className={'accountSidebarCard_create'}
- >
- Add Config Agent Account
- <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
- </Link>
- </DashboardCard>
+ {
+ !self.props.readonly ?
+ <DashboardCard className="accountSidebarCard">
+ <Link
+ to={{pathname: '/accounts/config-agent/create'}}
+ title="Create Config Agent Account"
+ className={'accountSidebarCard_create'}
+ onClick={self.props.actions.handleCancelAccount}
+ >
+ Add Config Agent Account
+ <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
+ </Link>
+ </DashboardCard>
+ : <div style={{margin:'1rem'}}></div>
+ }
</div>
);
return html;
AccountSidebar.defaultProps = {
cloud: [],
sdn: [],
- 'config-agent': []
+ 'config-agent': [],
+ ro: []
}
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
@import 'style/_colors.scss';
.accountSidebar {
-ms-flex:1 1 100%;
- flex:1 1 100%;
+ -webkit-box-flex:1;
+ flex:1 1 100%;
max-width:300px;
h1 {
margin-left:1rem;
background:white;
padding-left:0.5rem;
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-align:start;
- align-items:flex-start;
+ -webkit-box-align:start;
+ align-items:flex-start;
text-transform:uppercase;
h3 {
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex:1;
- flex:1;
+ -webkit-box-flex:1;
+ flex:1;
-ms-flex-pack:justify;
- justify-content:space-between;
+ -webkit-box-pack:justify;
+ justify-content:space-between;
-ms-flex-align: center;
- align-items: center;
+ -webkit-box-align: center;
+ align-items: center;
padding-left:0.5rem;
}
}
+ &--name {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ max-width: 170px;
+ }
&--content {
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-pack:start;
- justify-content:flex-start;
+ -webkit-box-pack:start;
+ justify-content:flex-start;
-ms-flex-align: center;
- align-items: center;
+ -webkit-box-align: center;
+ align-items: center;
height:70px;
}
&--logo{
a.link-item {
background-color: $dark-gray;
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
-ms-flex-direction: row;
- flex-direction: row;
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ flex-direction: row;
padding: 1rem;
-ms-flex-pack: start;
- justify-content: flex-start;
+ -webkit-box-pack: start;
+ justify-content: flex-start;
text-decoration: none;
color: black;
}
a.empty-pool {
margin-top:0.125rem;
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-pack: justify;
- justify-content: space-between;
+ -webkit-box-pack: justify;
+ justify-content: space-between;
img {
height:1rem;
}
}
&_create {
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-pack:justify;
- justify-content:space-between;
+ -webkit-box-pack:justify;
+ justify-content:space-between;
padding:1rem;
font-size: 0.85rem;
text-transform:uppercase;
margin-left: 1rem;
margin-bottom:1rem;
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-align:center;
- align-items:center;
+ -webkit-box-align:center;
+ align-items:center;
padding: 0.5rem;
cursor: pointer;
text-transform:uppercase;
var CompressionPlugin = require("compression-webpack-plugin");
// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
var config = {
devtool: 'source-map',
entry: mainPath,
},
plugins: [
new HtmlWebpackPlugin({
- filename: '../index.html',
- templateContent: '<div id="app"></div>'
+ filename: '../' + htmlFilename,
+ template: frameworkPath + '/plugin-index.html'
})
]
};
--- /dev/null
+#
+# 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.
+#
+# Author(s): Kiran Kashalkar
+# Creation Date: 08/18/2015
+#
+
+##
+# 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(
+ admin
+ DEPENDS skyquake
+ SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_COMMAND echo
+ BUILD_COMMAND
+ ${CMAKE_CURRENT_BINARY_DIR}/admin/admin-build/scripts/build.sh
+ INSTALL_COMMAND
+ ${CMAKE_CURRENT_SOURCE_DIR}/scripts/install.sh
+ ${CMAKE_CURRENT_BINARY_DIR}/admin/admin-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
+)
+
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+
+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 APIVersion = '/v1';
+var About = {};
+
+About.getVCS = function(req) {
+ var api_server = req.query["api_server"];
+
+ return new Promise(function(resolve, reject) {
+ var requestHeaders = {};
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: utils.confdPort(api_server) + APIVersion + '/api/operational/vcs/info?deep',
+ type: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ },
+ function(error, response, body) {
+ var data;
+ console.log(error);
+ if (utils.validateResponse('About/vcs.get', error, response, body, resolve, reject)) {
+ try {
+ data = JSON.parse(response.body)["rw-base:info"]
+ } catch (e) {
+ return reject({});
+ }
+
+ return resolve(data);
+ }
+ });
+ });
+}
+
+About.getVersion = function(req) {
+ var api_server = req.query["api_server"];
+
+ return new Promise(function(resolve, reject) {
+ var requestHeaders = {};
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: utils.confdPort(api_server) + APIVersion + '/api/operational/version?deep',
+ type: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ },
+ function(error, response, body) {
+ var data;
+ console.log(error);
+ if (utils.validateResponse('About/version.get', error, response, body, resolve, reject)) {
+ try {
+ data = JSON.parse(response.body)['rw-base:version']
+ } catch (e) {
+ return reject({});
+ }
+
+ return resolve(data);
+ }
+ });
+ });
+}
+
+About.get = function(req) {
+
+ var api_server = req.query["api_server"];
+
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ About.getVCS(req),
+ About.getVersion(req)
+ ])
+ .then(function(results) {
+ var AboutObject = {};
+ AboutObject.vcs = results[0];
+ AboutObject.version = results[1];
+ resolve(AboutObject);
+ }, function(error) {
+ console.log('error getting vcs data', error);
+ reject(error)
+ });
+ });
+};
+
+About.uptime = function(req) {
+ var api_server = req.query["api_server"];
+ return new Promise(function(resolve, reject) {
+ var requestHeaders = {};
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: utils.confdPort(api_server) + APIVersion + '/api/operational/uptime/uptime',
+ type: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false
+ },
+ function(error, response, body) {
+ if (utils.validateResponse('About.uptime', error, response, body, resolve, reject)) {
+ try {
+ data = JSON.parse(response.body);
+ } catch (e) {
+ return reject({});
+ }
+ return resolve(data)
+
+ }
+ })
+ })
+}
+module.exports = About;
--- /dev/null
+{
+ "root": "public",
+ "name": "Configuration",
+ "dashboard": "./admin.jsx",
+ "order": 5,
+ "priority": 2,
+ "admin_link": true,
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-rbac-platform:platform-admin",
+ "rw-rbac-platform:platform-oper"
+ ],
+ "routes": [{
+ "label": "Dashboard",
+ "route": "",
+ "component": "./admin.jsx",
+ "path": "",
+ "type": "internal",
+ "routes": [
+ {
+ "route": ":ref",
+ "component": "./admin.jsx",
+ "path": ":ref",
+ "type": "internal"
+ }
+ ]
+ }]
+}
\ No newline at end of file
--- /dev/null
+{
+ "name": "admin",
+ "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": {
+ "bluebird": "^3.4.1",
+ "change-case": "^3.0.1",
+ "express": "^4.13.3",
+ "history": "^1.17.0",
+ "jquery": "^2.2.1",
+ "json-loader": "^0.5.4",
+ "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-dialog": "^1.0.1",
+ "react-dom": "^0.14.6",
+ "react-modal": "^2.2.4",
+ "react-open-iconic-svg": "^1.0.4",
+ "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",
+ "validator": "^8.1.0"
+ },
+ "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",
+ "compression-webpack-plugin": "^0.3.2",
+ "cors": "^2.7.1",
+ "css-loader": "^0.23.1",
+ "file-loader": "^0.8.5",
+ "html-webpack-plugin": "^2.9.0",
+ "http-proxy": "^1.12.0",
+ "loaders.css": "^0.1.2",
+ "node-sass": "^3.4.2",
+ "react-addons-css-transition-group": "^0.14.7",
+ "sass-loader": "^3.1.2",
+ "style-loader": "^0.13.0",
+ "webpack": "^1.3.0",
+ "webpack-dev-server": "^1.10.1"
+ }
+}
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+var router = require('express').Router();
+var cors = require('cors');
+var utils = require('../../framework/core/api_utils/utils.js')
+var Admin = require('./api/admin.js');
+router.get('/api/', cors(), function(req, res) {
+ Admin.get(req).then(function(data) {
+ res.send(data);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+module.exports = router;
--- /dev/null
+#!/bin/bash
+
+#
+# Copyright 2016-2017 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.
+#
+
+PLUGIN_NAME=admin
+# change to the directory of this script
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+
+#
+# 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.
+#
+
+plugin=admin
+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 node_modules package.json -C $source_dir
+#cp -Lrp $source_dir/node_modules $bcache_dir/plugins/$plugin/.
+
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+
+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);
+});
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import Alt from 'widgets/skyquake_container/skyquakeAltInstance';
+
+const NSD_SCHEMA_PATH = 'nsd-catalog/nsd';
+const VNFD_SCHEMA_PATH = 'vnfd-catalog/vnfd';
+
+import {
+ modelActions
+} from 'source/model'
+
+class AdminStore {
+ constructor() {
+ this.adminActions = this.alt.generateActions(
+ 'openModel'
+ );
+ this.state = {
+ modelList: [
+ 'openidc-provider-config'
+ ]
+ }
+ this.bindActions(modelActions);
+
+ }
+
+ get actions() {
+ return this.adminActions
+ }
+ processRequestFailure(result){
+ console.debug('processRequestFailure');
+ Alt.actions.global.showNotification.defer(result.response.error.message);
+ }
+ processRequestSuccess(data){
+ console.debug('processRequestSuccess');
+ }
+ processRequestInitiated(data){
+ console.debug('processRequestInitiated');
+ }
+}
+
+export default AdminStore
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import React from 'react';
+import './admin.scss';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import AppHeader from 'widgets/header/header.jsx';
+import AdminStore from './AdminStore'
+import ModelStore from './store/ModelStore'
+import ModelExplorer from './components/ModelExplorer'
+
+import 'style/layout.scss';
+
+class Element {
+ constructor(explorerModel) {
+ this.explorerModel = explorerModel;
+ this.node = explorerModel.dataModel.path.split('/').pop().split(':').pop();
+ this.dataModel = explorerModel.dataModel;
+ if (this.dataModel.data && this.dataModel.schema[this.node].type === 'list' && !Array.isArray(this.dataModel.data)) {
+ this.dataModel.data = [this.dataModel.data];
+ }
+ }
+ get name() { return this.dataModel.path.split(':').pop() }
+ get type() { return this.dataModel.schema[this.node].type }
+ get value() { return this.dataModel.data }
+ get schema() { return this.dataModel.schema[this.node] }
+ getItemInfo(item) {
+ return ModelExplorer.getItemInfo(this.dataModel.schema[this.node].key, item);
+ }
+ // explore model methods
+ getElement = path => path.length > 1 ? this.explorerModel.getElement(path.slice(1)) : this;
+}
+
+
+class Admin extends React.Component {
+ constructor(props) {
+ super(props)
+ this.adminStore = props.flux.stores['AdminStore'] || props.flux.createStore(AdminStore, 'AdminStore');
+ this.adminStore.listen(this.updateAdminState);
+ this.state = { explorerModels: {}, ...this.adminStore.getState() };
+ }
+
+ componentDidMount() {
+ const storeList = this.state.modelList ? this.state.modelList.map((path, index) => {
+ const store = this.props.flux.stores[path] || this.props.flux.createStore(ModelStore, path, path);
+ store.listen(this.updateModelState);
+ store.get();
+ return store;
+ }) : [];
+ this.setState({ storeList });
+ }
+
+ componentWillUnmount() {
+ this.adminStore.unlisten(this.updateSchema);
+ this.state.storeList.forEach((store) => store.unlisten(this.updateModelState));
+ }
+
+ updateModelState = model => {
+ const explorerModels = Object.assign({}, this.state.explorerModels);
+ explorerModels[model.path] = new Element(ModelExplorer.getExplorerModel(model));
+ this.setState({ explorerModels });
+ }
+
+ updateAdminState = admin => {
+ // if storeList has changed then handle that
+ }
+
+ updateModel = (path, operation, data) => {
+ const store = this.props.flux.stores[path[1]] || this.props.flux.createStore(ModelStore, path[1]);
+ store[operation](path.slice(2), data);
+ return operation === 'delete'; // close column?
+ }
+
+ adminExplorerModel = new class {
+ constructor(admin) {
+ this.admin = admin;
+ }
+
+ getElement(path) {
+ if (path.length > 2) {
+ return this.admin.state.explorerModels[path[1]].getElement(path.slice(1))
+ } else if (path.length > 1) {
+ return this.admin.state.explorerModels[path[1]]
+ } else {
+ const data = Object.values(this.admin.state.explorerModels);
+ const properties = data.length ?
+ data.map(e => ({
+ name: e.dataModel.path,
+ type: ((e.dataModel.schema && e.dataModel.schema[e.node].type) || 'loading')
+ }))
+ : [];
+ const schema = {
+ name: "Configuration",
+ type: 'container',
+ properties
+ }
+ return {
+ schema,
+ value: data,
+ type: schema.type,
+ name: schema.name
+ }
+ }
+ }
+ }(this);
+
+ render() {
+ const { flux } = this.props;
+ const { storeList } = this.state;
+ const modelList = storeList && storeList.map(store => store.getState())
+ return (
+ <div className="admin-container">
+ <ModelExplorer model={this.adminExplorerModel} onUpdate={this.updateModel} />
+ </div>
+ )
+ }
+}
+export default SkyquakeComponent(Admin);
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+.admin-container {
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ align-content: stretch;
+ width: 100%;
+ flex: 1 1 100%;
+ overflow-y: auto;
+
+ .table-container-wrapper {
+ flex: 1;
+ .table-container {
+ padding:40px 20px 10px 20px;
+ font-family: 'roboto-thin', Helvetica, Arial, sans-serif;
+ }
+ }
+
+ table {
+ border-collapse: separate;
+ border-spacing: 0.0625rem solid;
+ width: 100%;
+
+ thead {
+ th {
+ background-color: #e5e5e5;
+ border-right: 1px solid #dadada;
+ padding: 1.125rem 1.125rem;
+ text-align:left;
+ font-size: .8rem;
+ font-weight: bold;
+ text-transform: uppercase;
+ }
+ td:last-child {
+ border: 0;
+ }
+ }
+ tbody {
+ td {
+ border-bottom: 1px solid #e5e5e5;
+ padding: .75rem 0rem .75rem 1.125rem;
+ text-align: left;
+ width: 40%;
+ font-size: .8rem;
+ }
+ tr:nth-child(even) td {
+ background-color: #e5e5e5;
+ }
+ }
+ }
+ h2 {
+ background-color: #ffffff;
+ margin-bottom: 0.125rem;
+ font-size: 0.9rem;
+ padding: 1.125rem;
+ font-weight: bold;
+ text-transform: uppercase;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+class Actions {
+
+ constructor() {
+ this.generateActions(
+ 'openModel');
+ }
+}
+export default Actions
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import adminActions from './adminActions.js';
+import Utils from 'utils/utils.js';
+import $ from 'jquery';
+var adminSource = {
+ get: function () {
+ return {
+ remote: function (state) {
+ return new Promise(function (resolve, reject) {
+ $.ajax({
+ url: 'api/?api_server=' + API_SERVER,
+ type: 'GET',
+ contentType: "application/json",
+ success: function (data) {
+ resolve(data);
+ },
+ error: function (error) {
+ console.log("There was an error getting the crash details: ", error);
+ reject(error);
+ }
+ }).fail(function (xhr) {
+ console.log(xhr)
+ });
+
+ }).catch(function (e) {
+ console.log(e)
+ });
+ },
+ success: adminActions.getAdminSuccess,
+ loading: adminActions.getAdminLoading,
+ error: adminActions.getAdminFail
+ }
+ },
+}
+module.exports = adminSource;
\ No newline at end of file
--- /dev/null
+import React from 'react'
+import { TrashIcon } from 'react-open-iconic-svg';
+import { PencilIcon } from 'react-open-iconic-svg';
+import { PlusIcon } from 'react-open-iconic-svg';
+import { CircleXIcon } from 'react-open-iconic-svg';
+
+const actionIcon = {
+ 'create': {icon: PlusIcon, title: "Add"},
+ 'update': {icon: PencilIcon, title: "Edit"},
+ 'delete': {icon: TrashIcon, title: "Delete"},
+ 'close': {icon: CircleXIcon, title: "Close Panel"}
+}
+
+export default (props) => {
+ const { isHidden, actions, handler } = props;
+ const buttons = [];
+ actions && actions.forEach((action => buttons.push(
+ <div key={action} style={{ visibility: isHidden ? 'hidden' : 'visible', display: 'inline-block', padding: '.15rem .5rem' }}>
+ <div title={actionIcon[action].title} style={{ display: 'inline-block', cursor: 'pointer' }}>
+ {React.createElement(actionIcon[action].icon, {width: '12', height: '12', onClick: () => handler(action)})}
+ </div>
+ </div>
+ )));
+ return (
+ <div className='button-bar' style={{ height: '17px', width: '100%', textAlign: 'right' }} >
+ {buttons}
+ </div>
+ )
+}
+
--- /dev/null
+import React from 'react'
+import ColumnCard from './ColumnCard'
+import { ExcerptIcon } from 'react-open-iconic-svg';
+
+export default class extends React.Component {
+ render() {
+ try {
+ const { model, path, isSelected, openElement } = this.props;
+ const element = model.getElement(path)
+ const name = element.name;
+ let choiceName = "none";
+ if (element.value) {
+ const selectedCase = element.schema.properties.find(c =>
+ c.properties && c.properties.some(p => element.value[p.name]));
+ if (selectedCase) {
+ choiceName = selectedCase.name;
+ }
+ }
+ console.debug(`ChoiceCard: ${name}`);
+ return (
+ <ColumnCard path={path} isSelected={isSelected} className='chioce-card'>
+ <ExcerptIcon />
+ <div className='list-card' style={{ cursor: 'pointer' }} onClick={() => openElement({ path })}>
+ <span style={{ paddingRight: '8px' }} >{`${name}`}</span><span style={{ fontSize: 'small', verticalAlign: 'top' }}>{`(${choiceName})`}</span>
+ </div>
+ </ColumnCard>
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
--- /dev/null
+import React from 'react'
+import ContainerColumn from './ContainerColumn'
+
+export default class extends ContainerColumn {
+ constructor(props) {
+ super(props);
+ const element = props.model.getElement(props.path);
+ const selectedCase = element.value ?
+ element.schema.properties.find(c => c.properties && c.properties.some(p => element.value[p.name]))
+ : null;
+ if (selectedCase) {
+ this.state.actions = ['delete'];
+ this.state.properties = selectedCase.properties;
+ } else {
+ this.state.actions = ['create'];
+ }
+ }
+ render() {
+ return super.render();
+ }
+}
+
--- /dev/null
+import React from 'react'
+
+export default class extends React.Component {
+ render() {
+ let { isSelected, path, children } = this.props;
+ const bgColor = ((isSelected && '#00acee !important') || 'inherit');
+ const className = ['card'];
+ isSelected && className.push('--selected');
+ const id = path.join('-') + className.join('-');
+ if (isSelected) {
+ children = (
+ <div style={{ backgroundColor: '#00acee !important', width: '100%' }} >
+ {children}
+ </div>
+ )
+ }
+ return (
+ <div className={className.join(' ')} style={{ margin: '1px', padding: '3px' }} >
+ {children}
+ </div>
+ )
+ }
+}
\ No newline at end of file
--- /dev/null
+import React from 'react'
+import ColumnCard from './ColumnCard'
+import { FolderIcon } from 'react-open-iconic-svg';
+
+export default class extends React.Component {
+ render() {
+ try {
+ const { model, path, isSelected, openElement } = this.props;
+ const element = model.getElement(path)
+ const name = element.name;
+ console.debug(`ContainerCard: ${name}`);
+ return (
+ <ColumnCard path={path} isSelected={isSelected} className='container-card'>
+ <FolderIcon/>
+ <div style={{cursor: 'pointer'}} onClick={() => openElement({ path })}>{name}</div>
+ </ColumnCard>
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
+
--- /dev/null
+import React from 'react'
+import LeafGroup from './LeafGroup'
+import ListStack from './ListStack'
+import ExplorerColumn from './ExplorerColumn'
+import yang from '../yang/leaf-utils'
+
+export default class extends React.Component {
+ constructor(props){
+ super(props);
+ this.state = {actions: [], properties: null};
+ }
+
+ render() {
+ try {
+ const { model, path, isLast, selected, isReadonly, openElement, editElement, columnCloser } = this.props;
+ const element = model.getElement(path)
+ const name = element.name;
+ console.debug(`ContainerColumn: ${name}`);
+ const data = element.value;
+ const leaves = [];
+ const choices = [];
+ const containers = [];
+ const lists = [];
+ const loading = [];
+ const dataDivided = {
+ leaf: leaves,
+ leaf_list: leaves,
+ container: containers,
+ list: lists,
+ choice: choices,
+ loading: loading
+ };
+ const properties = this.state.properties || element.schema.properties;
+ properties.forEach((property, index) => {
+ const list = dataDivided[property.type]
+ list && list.push(property)
+ !list && console.debug(`ContainerColumn - unhandled property : ${path}/${property.name} - ${property.type}`)
+ });
+ choices.forEach(choice => choice.properties.forEach(c => c.properties.forEach(p => leaves.push(p))));
+ const items = [];
+ leaves.length && items.push(<LeafGroup key='leaves' model={model} path={path} isReadonly={!isLast} properties={leaves} editElement={editElement} />);
+ containers.length && items.push(<ListStack key='containers' model={model} path={path} properties={containers} selected={selected} openElement={openElement} />);
+ lists.length && items.push(<ListStack key='list' model={model} path={path} properties={lists} selected={selected} openElement={openElement} />);
+ loading.length && items.push(<ListStack key='loading' model={model} path={path} properties={loading} openElement={openElement} />);
+ const actions = this.state.actions.slice();
+ !isLast || isReadonly || !leaves.length || leaves.every(p => yang.isKey(p)) || actions.push('update');
+ function invokeAction(action) {
+ editElement(path, action);
+ }
+ return (
+ <ExplorerColumn title={name || path} isLast={isLast} actions={actions} handler={invokeAction} columnCloser={columnCloser} >
+ <div className='container-column' >
+ <div>
+ {items}
+ </div>
+ </div>
+ </ExplorerColumn>
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
--- /dev/null
+import React from 'react'
+import Modal from 'react-modal'
+import Dialog from 'react-dialog'
+import LeafField from './editor/LeafField'
+import yang from '../yang/leaf-utils.js'
+import changeCase from 'change-case'
+
+const Button = ({ name, onClick, disabled }) => (
+ <button disabled={disabled}
+ style={{ padding: '.25rem .5rem', margin: '0 0.25rem', boxShadow: '1px 1px rgba(0, 0, 0, 0.15)' }}
+ onClick={onClick}>{name}</button>
+);
+
+const ButtonBar = ({ submitName, submitDisabled, onSubmit, onCancel }) => (
+ <div className='button-bar' style={{ width: '100%', textAlign: 'right', marginTop: '18px' }} >
+ <Button name="Cancel" onClick={onCancel} />
+ <Button name={submitName} onClick={onSubmit} disabled={submitDisabled} />
+ </div>
+);
+
+function initErrorInfo(leaves, container) {
+ return leaves.reduce((errorInfo, p) => {
+ const value = container && container[p.name];
+ const readOnly = p.isKey && !operation.isCreate;
+ if (!readOnly && p.mandatory && !value) {
+ errorInfo[p.name] = 'required';
+ }
+ return errorInfo;
+ }, {})
+}
+
+function buildChoiceProperty(choiceProperty) {
+ let description = choiceProperty.description + ' ';
+ const choices = choiceProperty.properties.reduce((o, p) => {
+ o[p.name] = { value: p.name };
+ description = `${description} ${p.name} - ${p.description}`;
+ return o;
+ }, {});
+ const choicePicker = Object.assign({}, choiceProperty);
+ choicePicker['type'] = 'leaf';
+ choicePicker['description'] = description;
+ choicePicker['data-type'] = {
+ enumeration: {
+ enum: choices
+ }
+ }
+ return choicePicker;
+}
+
+function getIntialState(props) {
+ const { isOpen, model, path, operation } = props;
+ const baseLeaves = [];
+ const choices = [];
+ const dataSet = {};
+ const errorInfo = {};
+ let shadowErrorInfo = {};
+ let leaves = baseLeaves;
+ if (isOpen && path && !operation.isDelete) {
+ const element = model.getElement(path);
+ const dataDivided = {
+ leaf: baseLeaves,
+ leaf_list: baseLeaves,
+ choice: choices,
+ };
+ element.schema.properties.forEach((property, index) => {
+ const list = dataDivided[property.type]
+ list && list.push(property)
+ });
+ if (!operation.isCreate) {
+ // we are not going to prompt for choices so add in appropriate files now
+ choices.forEach((choice) => {
+ const caseSelected = choice.properties.find(c => c.properties && c.properties.some(p => element.value[p.name]));
+ if (caseSelected) {
+ caseSelected.properties.forEach(p => yang.isLeafOrLeafList(p) && baseLeaves.push(p));
+ }
+ });
+ } else {
+ // on create we first propmt for "features"
+ leaves = choices.map(choice => buildChoiceProperty(choice));
+ }
+ shadowErrorInfo = initErrorInfo(leaves, element.value);
+ }
+ return { dataSet, shadowErrorInfo, errorInfo, baseLeaves, leaves, choices };
+}
+
+export default class extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = getIntialState(props);
+ this.state.showHelp = true;
+ }
+
+ handleCloseEditor = () => {
+ };
+
+ componentWillReceiveProps(nextProps) {
+ if (this.props.isOpen !== nextProps.isOpen) {
+ this.setState(getIntialState(nextProps));
+ }
+ }
+
+ render() {
+ try {
+ const { isOpen, model, isReadonly, properties, operation, onSave, onCancel } = this.props;
+ if (!isOpen) {
+ return null;
+ }
+ let dataPath = this.props.path.slice();
+ const element = model.getElement(dataPath);
+ const container = element.value;
+ let editors = null;
+ let submitHandler = () => {
+ if (Object.keys(this.state.errorInfo).length === 0) {
+ onSave(this.state.dataSet);
+ }
+ }
+ const checkForSubmitKey = (e) => {
+ if (e.keyCode == 13) {
+ e.preventDefault();
+ e.stopPropagation();
+ submitHandler();
+ }
+ };
+ let submitButtonLabel = operation.isCreate ? "Add" : "Save";
+ let submitDisabled = true;
+ let headerText = null;
+ if (operation.isDelete) {
+ const id = dataPath[dataPath.length - 1];
+ const deletePrompt = `Delete ${id}?`;
+ submitButtonLabel = "Delete";
+ submitDisabled = false;
+ editors = (<div style={{ paddingBottom: '10px' }}>{deletePrompt}</div>);
+ } else {
+ let { leaves, choices } = this.state;
+ if (choices.length) {
+ if (operation.isCreate) {
+ headerText = `Select feature(s) for ${element.name}`
+ submitButtonLabel = "Continue";
+ submitHandler = () => {
+ const { dataSet, baseLeaves } = this.state;
+ const leaves = choices.reduce((leafList, choice) => {
+ const caseSelected = dataSet[choice.name].value;
+ delete dataSet[choice.name];
+ if (caseSelected) {
+ return leafList.concat(choice.properties.find((p) => p.name === caseSelected).properties.reduce((list, p) => {
+ yang.isLeafOrLeafList(p) && list.push(p);
+ return list;
+ }, []));
+ }
+ return leafList;
+ }, baseLeaves.slice());
+ const shadowErrorInfo = initErrorInfo(leaves, element.value);
+ this.setState({ dataSet, leaves, choices: [], errorInfo: {}, shadowErrorInfo });
+ }
+ }
+ }
+ submitDisabled = (Object.keys(this.state.shadowErrorInfo).length
+ || (!operation.isCreate && Object.keys(this.state.dataSet).length === 0));
+ // process the named field value change
+ function processFieldValueChange(property, currentValue, value) {
+ console.debug(`processed change for -- ${name} -- with value -- ${value}`);
+ const dataSet = this.state.dataSet;
+ const name = property.name;
+ if ((currentValue && currentValue !== value) || (!currentValue && value)) {
+ dataSet[name] = { property, value, currentValue };
+ } else {
+ delete dataSet[name];
+ }
+ const { errorInfo, shadowErrorInfo } = this.state;
+ delete errorInfo[name];
+ delete shadowErrorInfo[name];
+ this.setState({ dataSet, errorInfo, shadowErrorInfo });
+ }
+
+ function onErrorHandler(name, message) {
+ const { errorInfo, shadowErrorInfo } = this.state;
+ errorInfo[name] = message;
+ shadowErrorInfo[name] = message;
+ this.setState({ errorInfo, shadowErrorInfo });
+ }
+
+ editors = leaves.reduce((editors, property, index) => {
+ const itemPath = dataPath.slice();
+ itemPath.push(property.name);
+ const props = { model, 'path': itemPath };
+ const value = container && container[property.name];
+ let readOnly = isReadonly;
+ let extraHelp = null;
+ if (!isReadonly) {
+ if (yang.isKey(property) && !operation.isCreate) {
+ extraHelp = "Id fields are not modifiable.";
+ readOnly = true;
+ } else if (yang.isLeafList(property)) {
+ extraHelp = "Enter a comma separated list of values."
+ }
+ }
+ editors.push(
+ <LeafField
+ key={property.name}
+ container={container}
+ property={property}
+ path={dataPath}
+ value={value}
+ showHelp={this.state.showHelp}
+ onChange={processFieldValueChange.bind(this, property, value)}
+ onError={onErrorHandler.bind(this, property.name)}
+ readOnly={readOnly}
+ extraHelp={extraHelp}
+ errorMessage={this.state.errorInfo[property.name]}
+ />
+ );
+ return editors;
+ }, [])
+ }
+ const customStyles = {
+ content: {
+ top: '50%',
+ left: '50%',
+ right: 'auto',
+ bottom: 'auto',
+ marginRight: '-50%',
+ transform: 'translate(-50%, -50%)'
+ }
+ };
+ const dlgHeader = headerText ? (<div style={{ paddingBottom: '16px' }} >{headerText}</div>) : null;
+ return (
+ <Modal
+ isOpen={isOpen}
+ contentLabel="Edit"
+ onRequestClose={onCancel}
+ shouldCloseOnOverlayClick={false}
+ style={customStyles}
+ >
+ <div className='leaf-group' style={{ maxWidth: '400px', maxHeight: '600px' }} onKeyUp={checkForSubmitKey} >
+ {dlgHeader}
+ {editors}
+ <ButtonBar
+ submitName={submitButtonLabel}
+ submitDisabled={submitDisabled}
+ onSubmit={submitHandler}
+ onCancel={onCancel} />
+ </div>
+ </Modal >
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+import React from 'react'
+import ActionBar from './ActionBar'
+
+export default ({ title, children, columnCloser, isLast, actions, handler }) => {
+ let actionBar = null;
+ if (isLast) {
+ if (columnCloser) {
+ actions = actions ? actions.slice() : [];
+ actions.push('close');
+ }
+ actionBar = (<ActionBar actions={actions} handler={(operation) => operation === 'close' ? columnCloser() : handler(operation)} />);
+ } else {
+ actionBar = (<ActionBar />);
+ }
+ return (
+ <div className='column' style={{ backgroundColor: 'gainsboro', height: '100%', display: 'flex', flexDirection: 'column', marginLeft: '4px', marginRight: '4px' }} >
+ <h2 style={{ marginBottom: '4px' }} >{title}</h2>
+ {actionBar}
+ {children}
+ </div>
+ )
+}
+
--- /dev/null
+import React from 'react'
+import ColumnCard from './ColumnCard'
+import yang from '../yang/leaf-utils.js'
+
+export default class extends React.Component {
+
+ render() {
+ try {
+ const { model, path, isReadonly, properties, editElement } = this.props;
+ const element = model.getElement(path);
+ console.debug(`LeafGroup: ${element.name}`);
+ const container = element.value;
+ const leaves = properties.reduce((leaves, property, index) => {
+ const itemPath = path.slice();
+ itemPath.push(property.name);
+ const props = { model, 'path': itemPath };
+ let value = (container && container[property.name]);
+ let valueIsSet = yang.isValueSet(property, value);
+ if (!valueIsSet) {
+ value = yang.getDefaultValue(property);
+ valueIsSet = yang.isValueSet(property, value);
+ }
+ if (valueIsSet) {
+ value = yang.getDisplayValue(property, value);
+ }
+ valueIsSet && leaves.push(
+ <div key={property.name} className='leaf-group-leaf' style={{ maxWidth: '400px' }} >
+ <div style={{ fontSize: 'small', color: 'gray' }} >{`${property.name}`}</div>
+ <div style={{paddingLeft: '4px'}} >{value}</div>
+ </div>
+ );
+ return leaves;
+ }, [])
+ return (
+ <ColumnCard path={path}>
+ <div className='leaf-group'>
+ {leaves}
+ </div>
+ </ColumnCard>
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
--- /dev/null
+import React from 'react'
+import ColumnCard from './ColumnCard'
+import { ListIcon } from 'react-open-iconic-svg';
+
+export default class extends React.Component {
+ render() {
+ try {
+ const { model, path, isSelected, openElement } = this.props;
+ const element = model.getElement(path);
+ const name = element.name;
+ const numItems = element.value ? Array.isArray(element.value) ? element.value.length : 1 : 0;
+ console.debug(`ListCard: ${name}`);
+ return (
+ <ColumnCard path={path} isSelected={isSelected} >
+ <ListIcon />
+ <div className='list-card' style={{cursor: 'pointer'}} onClick={() => openElement({ path })}>
+ <span style={{ paddingRight: '8px' }} >{`${name}`}</span><span style={{ fontSize: 'small', verticalAlign: 'top' }}>{`(${numItems})`}</span>
+ </div>
+ </ColumnCard>
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
--- /dev/null
+import React from 'react'
+import ListStack from './ListStack'
+import ListEntryCard from './ListEntryCard'
+import ExplorerColumn from './ExplorerColumn'
+import changeCase from 'change-case'
+
+export default class extends React.Component {
+
+ render() {
+ try {
+ const { model, path, isLast, selected, openElement, editElement, columnCloser } = this.props;
+ const element = model.getElement(path);
+ const name = element.name;
+ const shortName = changeCase.titleCase(name).split(' ').pop();
+ const buttonName = `Add ${shortName}`;
+ console.debug(`ListColumn: ${name}`);
+ const list = element.value ? Array.isArray(element.value) ? element.value : [element.value] : null;
+ const cards = list && list.map((item, index) => {
+ const itemInfo = element.getItemInfo(item);
+ const isSelected = selected ? selected.every((p,i) => p === itemInfo.path[i]) : false;
+ const itemPath = path.slice();
+ itemPath.push(itemInfo.path);
+ const props = { model, 'path': itemPath, 'openElement': openElement.bind(this, itemPath) };
+ return (
+ <ListEntryCard key={itemInfo.path}
+ model={model}
+ path={itemPath}
+ name={itemInfo.name}
+ isSelected={isSelected}
+ openElement={openElement.bind(this, itemPath)}
+ deleteElement={(path) => editElement(path, 'delete')} />
+ )
+ })
+ return (
+ <ExplorerColumn title={name || path} isLast={isLast} actions={['create']} handler={() => editElement(path, 'create')} columnCloser={columnCloser} >
+ <div className='list-column' >
+ {cards}
+ </div>
+ </ExplorerColumn>
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
--- /dev/null
+import React from 'react'
+import ColumnCard from './ColumnCard'
+import { DocumentIcon } from 'react-open-iconic-svg';
+
+export default class extends React.Component {
+
+ render() {
+ try {
+ const { model, path, name, isSelected, openElement } = this.props;
+ console.debug(`ListEntryCard: ${name}`);
+ return (
+ <ColumnCard path={path} isSelected={isSelected} >
+ <DocumentIcon />
+ <div className='list-entry-card' style={{cursor: 'pointer'}} onClick={() => openElement({ path })}>
+ <div>{`${name}`}</div>
+ </div>
+ </ColumnCard>
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
--- /dev/null
+import React from 'react'
+import ContainerColumn from './ContainerColumn'
+
+export default class extends ContainerColumn {
+ constructor(props){
+ super(props);
+ this.state.actions = ['delete'];
+ }
+}
--- /dev/null
+import React from 'react'
+import ListCard from './ListCard'
+import ContainerCard from './ContainerCard'
+import ChoiceCard from './ChoiceCard'
+import LoadingCard from './LoadingCard'
+
+const cardComponent = {
+ list: ListCard,
+ container: ContainerCard,
+ choice: ChoiceCard,
+ loading: LoadingCard
+}
+export default class extends React.Component {
+
+ render() {
+ try {
+ const { model, path, properties, selected, openElement } = this.props;
+ const element = model.getElement(path);
+ const container = element.value;
+ console.debug(`PropertiesStack: ${properties.length}`);
+ const cards = properties.map((property, index) => {
+ const isSelected = selected ? selected === property.name : false;
+ const itemPath = path.slice();
+ itemPath.push(property.name);
+ const props = { model, 'path': itemPath, isSelected, 'openElement': openElement.bind(this, itemPath) };
+ return (
+ <div key={property.name}>
+ {React.createElement(cardComponent[property.type], props)}
+ </div>
+ )
+ })
+ return (
+ <div>
+ {cards}
+ </div>
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
--- /dev/null
+import React from 'react'
+import ColumnCard from './ColumnCard'
+
+class LoadingCard extends React.Component {
+ render() {
+ try {
+ const { model, path } = this.props;
+ const name = model.getElement(path).name;
+ console.debug(`LoadingCard: ${name}`);
+ return (
+ <ColumnCard path={path}>
+ <div>{`Loading ${name}`}</div>
+ </ColumnCard>
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
+
+export default LoadingCard
\ No newline at end of file
--- /dev/null
+import React from 'react'
+import ExplorerColumn from './ExplorerColumn'
+
+export default class extends React.Component {
+
+ render() {
+ try {
+ const { model, path, isLast, openElement } = this.props;
+ console.debug(`LoadingColumn: ${path.join()}`);
+ return (
+ <ExplorerColumn title={name || path} isLast={isLast} >
+ <div className='loading-column'>
+ <h3>Loading</h3>
+ </div>
+ </ExplorerColumn>
+ )
+ } catch (e) {
+ console.error("component render", e);
+ }
+ }
+}
--- /dev/null
+import React from 'react'
+import ModalDialog from 'react-modal'
+import _set from 'lodash/set';
+import _get from 'lodash/get';
+import ContainerColumn from './ContainerColumn'
+import ChoiceColumn from './ChoiceColumn'
+import ListColumn from './ListColumn'
+import LoadingColumn from './LoadingColumn'
+import ListEntryColumn from './ListEntryColumn'
+import LoadingCard from './LoadingCard'
+import ListCard from './ListCard'
+import ContainerCard from './ContainerCard'
+import EditorDialog from './EditorDialog'
+
+function findItemInList(list, key, keyValue) {
+ const keyPath = Array.isArray(keyValue) ? keyValue : key.length > 1 ? JSON.parse(keyValue) : [keyValue];
+ if (key.length > 1) {
+ return list.find(item => {
+ return key.every((k, i) => item[k] === keyPath[i]);
+ });
+ } else {
+ const leaf = key[0];
+ const match = keyPath[0];
+ return list.find(item => {
+ return item[leaf] === match;
+ });
+ }
+}
+function getItemInfoFunction(schema) {
+ return function (item) {
+ return ModelExplorer.getItemInfo(schema.key, item);
+ }
+}
+
+function makeNameFromKeyPath(key, path, name) {
+ return path.length > 1 ? `${path[0]} (${path.slice(1).join(" ,")})` : name || path[0];
+}
+
+class ExplorerModel {
+ constructor(dataModel) {
+ this.dataModel = dataModel;
+ this.topNode = dataModel.path.split('/').pop();
+ }
+ getElement(path) {
+ const dataModel = this.dataModel;
+ if (dataModel.isLoading) {
+ return null;
+ }
+ if (dataModel.updatingPath && dataModel.updatingPath.every((p, i) => path[i] === p)) {
+ return { type: 'loading' }
+ }
+ return path.reduce((parent, node, index) => {
+ const element = Object.assign({}, parent);
+ element.path.push(node);
+ if (parent.type === 'list') {
+ element.type = 'list-entry'
+ element.value = findItemInList(parent.value, parent.schema.key, node);
+ element.keyValue = parent.schema.key.map(leaf => element.value[leaf]);
+ element.name = makeNameFromKeyPath(parent.schema.key, element.keyValue, element.value['name']);
+ element.getItemInfo = getItemInfoFunction(parent.schema);
+ } else {
+ element.schema = parent.schema.properties.find(property => property.name === node)
+ element.type = element.schema.type;
+ element.value = element.type === 'choice' ? parent.value : parent.value && parent.value[node];
+ element.name = node.split(':').pop();
+ if (element.type === 'list') {
+ element.getItemInfo = getItemInfoFunction(element.schema);
+ }
+ }
+ return element;
+ }, {
+ schema: dataModel.schema[this.topNode],
+ getItemInfo: getItemInfoFunction(this.schema),
+ value: dataModel.data,
+ type: dataModel.schema[this.topNode].type,
+ path: dataModel.path.split('/')
+ }
+ )
+ }
+}
+
+const columnComponent = {
+ 'list': ListColumn,
+ 'container': ContainerColumn,
+ 'list-entry': ListEntryColumn,
+ 'choice': ChoiceColumn,
+ 'loading': LoadingColumn
+}
+
+class ModelExplorer extends React.Component {
+ static getExplorerModel(dataModel) {
+ return new ExplorerModel(dataModel);
+ }
+ static getItemInfo(keyDef, item) {
+ const path = keyDef.map(leaf => item[leaf]);
+ const name = makeNameFromKeyPath(keyDef, path, item.name);
+ return {
+ path,
+ name
+ };
+ }
+
+ constructor(props) {
+ super(props);
+ const columns = props.columns || [['/']];
+ this.state = { columns };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (!this.state.columns && nextProps.model) {
+ this.setState({ columns: [[Array.isArray(nextProps.model) ? '' : '/']] })
+ }
+ }
+
+ render() {
+ const { model, onUpdate } = this.props;
+ let { columns, isEditMode, editPath, editOperation } = this.state;
+
+ const openElement = (col, path) => {
+ columns = columns.slice(0, col + 1);
+ columns.push(path);
+ this.setState({ columns })
+ }
+
+ const closeLastColumn = () => {
+ columns = columns.slice();
+ columns.pop();
+ this.setState({ columns })
+ }
+
+ const lastCol = columns.length - 1;
+ const modelColumns = columns && columns.map((path, col) => {
+ const open = openElement.bind(this, col);
+ const props = {
+ key: path.join('/'),
+ model,
+ path,
+ selected: col < lastCol ? columns[col + 1][path.length] : null,
+ isLast: col === lastCol,
+ openElement: openElement.bind(this, col),
+ columnCloser: col === lastCol && col && closeLastColumn,
+ editElement: (path, op) => this.setState({
+ isEditMode: true,
+ editOperation: op || 'update',
+ editPath: path
+ })
+ }
+ return React.createElement(columnComponent[model.getElement(path).type], props)
+ })
+
+ const updateModel = (data) => {
+ let { columns, isEditMode, editPath, editOperation } = this.state;
+ onUpdate(editPath, editOperation, data) && columns.pop();
+ this.setState({
+ columns,
+ isEditMode: false,
+ editOperation: null,
+ editPath: null
+ });
+ }
+
+ return (
+ <div className='model-explorer'>
+ <div style={{ width: '100%', display: 'flex', flexDirection: 'row', overflow: 'scroll' }}>
+ {modelColumns}
+ </div>
+ <EditorDialog
+ isOpen={isEditMode}
+ operation={{
+ isCreate: editOperation === 'create',
+ isDelete: editOperation === 'delete'}}
+ model={model}
+ path={editPath}
+ onCancel={() => this.setState({ isEditMode: false })}
+ onSave={updateModel} />
+ </div>
+ )
+ }
+}
+
+export default ModelExplorer
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2016-2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import _debounce from 'lodash/debounce';
+import React from 'react'
+import ClassNames from 'classnames'
+import changeCase from 'change-case'
+import _isInt from 'validator/lib/isInt'
+import _toInt from 'validator/lib/toInt'
+import _isFloat from 'validator/lib/isFloat'
+import _toFloat from 'validator/lib/toFloat'
+import _trim from 'validator/lib/trim'
+import _isIP from 'validator/lib/isIP'
+import yang from '../../yang/leaf-utils.js'
+import resolveLeafRefPaths from './resolveLeafRef'
+import Select from './Select'
+
+function validateRequired(isRequired, value) {
+ value = value.trim();
+ return isRequired && !value ? { success: false, message: "A value is required." } : { success: true, value: null };
+}
+
+function editorExitHandler(isValueRequired, onExit, onError, event) {
+ const value = event.target.value;
+ const result = validateRequired(isValueRequired, value);
+ onExit && onExit(result);
+}
+
+function Enumeration(props) {
+ const { id, property, title, readOnly, onChange, onError, onExit } = props;
+ let value = props.value;
+ const enumDef = property['data-type'].enumeration.enum;
+ const enumeration = typeof enumDef === 'string' ?
+ [{ name: enumDef, value: enumDef, isSelected: String(value) === enumDef }]
+ : Object.keys(enumDef).map(enumName => {
+ let enumValue = enumName;
+ return { name: enumName, value: enumValue, isSelected: String(enumValue) === String(value) };
+ });
+ const hasDefaultValue = !!yang.getDefaultValue(property);
+ const required = yang.isRequired(property) || hasDefaultValue;
+ if (!value && hasDefaultValue) {
+ value = yang.getDefaultValue(property);
+ }
+ return (
+ <Select
+ id={id}
+ value={value}
+ options={enumeration}
+ title={title}
+ placeholder={property.name}
+ onChange={onChange}
+ onExit={editorExitHandler.bind(null, required, onExit, onError)}
+ required={required} r
+ readOnly={readOnly} />
+ );
+}
+
+function Reference(props) {
+ const { id, property, title, value, path, model, readOnly, onChange, onError, onExit } = props;
+
+ function getLeafRef(property = {}, path, value, model) {
+ const leafRefPath = property['data-type']['leafref']['path'];
+
+ let leafRefPathValues = []; //resolveLeafRefPath(model, path, leafRefPath);
+
+ let leafRefObjects = [];
+
+ leafRefPathValues && leafRefPathValues.map((leafRefPathValue) => {
+ leafRefObjects.push({
+ name: leafRefPathValue,
+ value: leafRefPathValue,
+ isSelected: String(leafRefPathValue) === String(value)
+ });
+ });
+
+ return leafRefObjects;
+ }
+ const leafRefPathValues = getLeafRef(property, path, value, model);
+ const required = yang.isRequired(property);
+
+ return (
+ <Select
+ id={id}
+ value={value}
+ options={leafRefPathValues}
+ title={title}
+ placeholder={property.name}
+ onChange={onChange}
+ onExit={editorExitHandler.bind(null, required, onExit, onError)}
+ required={required}
+ readOnly={readOnly} />
+ );
+}
+
+function Boolean(props) {
+ const { id, property, title, readOnly, onChange, onError, onExit } = props;
+ let value = props.value;
+ const typeOfValue = typeof value;
+ if (typeOfValue === 'number' || typeOfValue === 'boolean') {
+ value = value ? 'TRUE' : 'FALSE';
+ } else if (value) {
+ value = value.toUpperCase();
+ }
+ const options = [
+ { name: "TRUE", value: 'TRUE' },
+ { name: "FALSE", value: 'FALSE' }
+ ]
+ const hasDefaultValue = !!yang.getDefaultValue(property);
+ const required = yang.isRequired(property) || hasDefaultValue;
+ if (!value && hasDefaultValue) {
+ value = yang.getDefaultValue(property);
+ }
+ return (
+ <Select
+ id={id}
+ value={value}
+ options={options}
+ title={title}
+ placeholder={property.name}
+ onChange={onChange}
+ onExit={editorExitHandler.bind(null, required, onExit, onError)}
+ required={required}
+ readOnly={readOnly} />
+ );
+}
+
+function Empty(props) {
+ // A null value indicates the leaf exists (as opposed to undefined).
+ // We stick in a string when the user actually sets it to simplify things
+ // but the correct thing happens when we serialize to user data
+ const EMPTY_LEAF_PRESENT = '--empty-leaf-set--';
+ const { id, property, value, title, readOnly, onChange } = props;
+ let isEmptyLeafPresent = !!value;
+ let present = isEmptyLeafPresent ? EMPTY_LEAF_PRESENT : "";
+ const options = [
+ { name: "Enabled", value: EMPTY_LEAF_PRESENT }
+ ]
+
+ return (
+ <Select
+ id={id}
+ value={present}
+ placeholder={"Not Enabled"}
+ options={options}
+ title={title}
+ onChange={onChange}
+ readOnly={readOnly} />
+ );
+}
+
+function getValidator(property) {
+ function validateInteger(constraints, value) {
+ return _isInt(value, constraints) ? { success: true, value: _toInt(value) } :
+ { success: false, value, message: "The value is not an integer or does not meet the property constraints." };
+ }
+ function validateDecimal(constraints, value) {
+ return _isFloat(value, constraints) ? { success: true, value: _toFloat(value) } :
+ { success: false, value, message: "The value is not a decimal number or does not meet the property constraints." };
+ }
+ function validateProperty(validator, errorMessage, value) {
+ return validator(value) ? { success: true, value } :
+ { success: false, value, message: errorMessage };
+ }
+ const name = property.name;
+ if (name === 'ip-address' || name.endsWith('-ip-address')) {
+ return validateProperty.bind(null, _isIP, "The value is not a valid ip address.")
+ }
+ switch (property['data-type']) {
+ case 'int8':
+ return validateInteger.bind(null, { min: -128, max: 127 });
+ case 'int16':
+ return validateInteger.bind(null, { min: -32768, max: 32767 });
+ case 'int32':
+ return validateInteger.bind(null, { min: -2147483648, max: 2147483647 });
+ case 'int64':
+ return validateInteger.bind(null, null);
+ case 'uint8':
+ return validateInteger.bind(null, { min: 0, max: 255 });
+ case 'uint16':
+ return validateInteger.bind(null, { min: 0, max: 65535 });
+ case 'uint32':
+ return validateInteger.bind(null, { min: 0, max: 4294967295 });
+ case 'uint64':
+ return validateInteger.bind(null, { min: 0 });
+ case 'decimal64':
+ return validateDecimal.bind(null, null)
+ case 'string':
+ default:
+ return function (value) { return { success: true, value } };
+ }
+}
+
+function messageTemplate(strings, ...keys) {
+ return (function (...vars) {
+ let helpInfo = vars.reduce((o, info) => Object.assign(o, info), {});
+ return keys.reduce((s, key, i) => {
+ return s + helpInfo[key] + strings[i + 1];
+ }, strings[0]);
+ });
+}
+
+const errorMessage = messageTemplate`"${'value'}" is ${'error'}. ${'message'}`;
+
+const inputDemensionStyle = { maxWidth: '100%', minWidth: '100%' };
+
+class Input extends React.Component {
+ constructor(props) {
+ super(props);
+ let originalValue = yang.isValueSet(props.property, props.value) ? props.value : null; // normalize empty value
+ this.state = { originalValue };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const { value } = nextProps
+ if (value !== this.state.originalValue) {
+ let originalValue = value ? value : null; // normalize empty value
+ this.setState({ originalValue })
+ }
+ }
+
+ render() {
+ const { id, property, value, title, readOnly, onChange, onError, onExit } = this.props;
+ const { originalValue } = this.state;
+ const placeholder = property.name;
+ const required = yang.isRequired(property);
+ const className = ClassNames(property.name + '-input', { '-is-required': required });
+
+ const validator = getValidator(property);
+ function handleValueChanged(newValue) {
+ newValue = newValue.trim();
+ const result = !newValue ? validateRequired(required, newValue) : validator(newValue);
+ result.success ? onChange(result.value) : onError(result.message);
+ }
+ const changeHandler = _debounce(handleValueChanged, 2000);
+ function onInputChange(e) {
+ e.preventDefault();
+ changeHandler(_trim(e.target.value));
+ }
+ function onBlur(e) {
+ changeHandler.cancel();
+ const value = _trim(e.target.value);
+ const result = !value ? validateRequired(required, value) : validator(value);
+ // just in case we missed it by cancelling the debouncer
+ result.success ? onChange(result.value) : onError(result.message);
+ onExit(result);
+ }
+ // if (!yang.isKey(property) && yang.isString(property)) {
+ // return (
+ // <textarea
+ // cols="5"
+ // id={id}
+ // defaultValue={value}
+ // placeholder={placeholder}
+ // className={className}
+ // onChange={onInputChange}
+ // onBlur={onBlur}
+ // required={required}
+ // readOnly={readOnly}
+ // style={inputDemensionStyle}/>
+ // );
+ // }
+ return (
+ <input
+ id={id}
+ type="text"
+ defaultValue={value}
+ className={className}
+ placeholder={placeholder}
+ onChange={onInputChange}
+ onBlur={onBlur}
+ required={required}
+ readOnly={readOnly}
+ style={inputDemensionStyle}
+ />
+ );
+ }
+}
+
+export {
+ Input,
+ Empty,
+ Boolean,
+ Reference,
+ Enumeration
+};
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import React from 'react';
+import ClassNames from 'classnames'
+import changeCase from 'change-case'
+import yang from '../../yang/leaf-utils.js'
+import { Input, Empty, Boolean, Reference, Enumeration } from './LeafEditor'
+
+// import '../../styles/EditDescriptorModelProperties.scss'
+
+function buildEditor(model, path, property, value, readOnly, id, onChange, onError, onExit) {
+ const title = path.join('.');
+
+ let editor = null;
+ id = id || property.name;
+ if (yang.isEnumeration(property)) {
+ editor = <Enumeration
+ property={property}
+ id={id}
+ value={value}
+ title={title}
+ onChange={onChange} onError={onError} onExit={onExit}
+ readOnly={readOnly}
+ />
+ } else if (yang.isLeafRef(property)) {
+ editor = <Reference
+ model={model}
+ property={property}
+ path={path}
+ value={value}
+ id={id}
+ title={title}
+ onChange={onChange} onError={onError} onExit={onExit}
+ readOnly={readOnly}
+ />
+ } else if (yang.isBoolean(property)) {
+ editor = <Boolean
+ property={property}
+ id={id}
+ value={value}
+ title={title}
+ onChange={onChange} onError={onError} onExit={onExit}
+ readOnly={readOnly}
+ />
+ } else if (yang.isLeafEmpty(property)) {
+ editor = <Empty
+ property={property}
+ id={id}
+ value={value}
+ title={title}
+ onChange={onChange} onError={onError} onExit={onExit}
+ readOnly={readOnly}
+ />
+ } else {
+ editor = <Input
+ property={property}
+ id={id}
+ value={value}
+ title={title}
+ onChange={onChange} onError={onError} onExit={onExit}
+ readOnly={readOnly}
+ />
+ }
+ return editor;
+}
+
+export default class LeafField extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { showHelp: props.showHelp, isInError: props.errorMessage };
+ }
+ componentWillReceiveProps(nextProps) {
+ const { showHelp, errorMessage } = nextProps
+ if (showHelp !== this.state.showHelp && !this.state.isInError) {
+ this.setState({ showHelp })
+ }
+ if (errorMessage !== this.state.errorMessage) {
+ this.setState({ showHelp: !!errorMessage || showHelp, isInError: errorMessage })
+ }
+ }
+ render() {
+ const { model, path, property, value, id, readOnly, extraHelp, onChange, onError } = this.props;
+ let title = changeCase.titleCase(property.name);
+ const showHelp = this.state.showHelp;
+ const description = (extraHelp ? extraHelp + ' ' : '') + property.description;
+ const helpText = this.state.isInError ? this.state.isInError + " " + description : description;
+ if (property.mandatory && !readOnly) {
+ title = "* " + title;
+ }
+ const errorHandler = (message) => {
+ this.setState({ showHelp: true, isInError: message });
+ }
+ const changeHandler = (value) => {
+ this.setState({ showHelp: this.props.showHelp, isInError: false });
+ onChange && onChange(value);
+ }
+ const exitHandler = (exitResult) => {
+ if (!exitResult.success) {
+ // errorHandler(exitResult.message);
+ onError && onError(exitResult.message);
+ }
+ }
+
+ const editor = buildEditor(
+ model, path, property, value, readOnly, id,
+ changeHandler, errorHandler, exitHandler);
+ const helpStyle = {
+ display: showHelp ? 'inline-block' : 'none',
+ paddingTop: '2px',
+ paddingLeft: '8px',
+ color: this.state.isInError ? 'red' : 'darkgray',
+ fontSize: 'small'
+ };
+ return (
+ <div className={ClassNames('leaf-property -is-leaf property')} style={{paddingBottom: '10px'}} >
+ <h3 className="property-label">
+ <label htmlFor={id}>
+ <span className={'leaf-name name info'}>{title}</span>
+ </label>
+ </h3>
+ <div className={ClassNames('property-content')}>
+ {editor}
+ </div>
+ <span className={'leaf-description description'}
+ style={helpStyle}>{helpText}</span>
+ </div>
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2016-2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react'
+import ClassNames from 'classnames'
+import changeCase from 'change-case'
+
+// import '../../styles/EditDescriptorModelProperties.scss'
+
+export default function Select(props) {
+ const { id, label, value, options, title, required, readOnly, onChange } = props;
+ const selectOptions = options.map((o, i) => <option key={':' + i} value={o.value}>{o.name}</option>);
+ if (!value || !required) {
+ let placeholder = props.placeholder || " ";
+ placeholder = changeCase.title(placeholder);
+ const noValueDisplayText = placeholder;
+ selectOptions.unshift(<option key={'(value-not-set)'} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
+ }
+ function onSelectChange(e) {
+ e.preventDefault();
+ onChange(e.target.value);
+ }
+ return (
+ <select
+ id={id}
+ style={{minWidth: '100%', maxWidth: '100%'}}
+ defaultValue={value}
+ title={title}
+ onChange={onSelectChange}
+ required={required}
+ disabled={readOnly}>
+ {selectOptions}
+ </select>
+ );
+}
--- /dev/null
+function isRelativePath(path) {
+ if (path.split('/')[0] == '..') {
+ return true;
+ }
+ return false;
+}
+
+function getResults(topLevelObject, pathArray) {
+ let objectCopy = _cloneDeep(topLevelObject);
+ let i = pathArray.length;
+ let results = [];
+
+ while (pathArray[pathArray.length - i]) {
+ if (_isArray(objectCopy[pathArray[pathArray.length - i]])) {
+ if (i == 2) {
+ results = _map(objectCopy[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]);
+ } else {
+ objectCopy = objectCopy[pathArray[pathArray.length - i]];
+ }
+ } else if (_isArray(objectCopy)) {
+ objectCopy.map((object) => {
+ if (_isArray(object[pathArray[pathArray.length - i]])) {
+ if (i == 2) {
+ results = results.concat(_map(object[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]));
+ }
+ }
+ })
+ }
+ i--;
+ }
+
+ return results;
+}
+
+function getAbsoluteResults(topLevelObject, pathArray) {
+ let i = pathArray.length;
+ let objectCopy = _cloneDeep(topLevelObject);
+ let results = [];
+
+ let fragment = pathArray[pathArray.length - i]
+
+ while (fragment) {
+ if (i == 1) {
+ // last fragment
+ if (_isArray(objectCopy)) {
+ // results will be obtained from a map
+ results = _map(objectCopy, fragment);
+ } else {
+ // object
+ if (fragment.match(/\[.*\]/g)) {
+ // contains a predicate
+ // shouldn't reach here
+ console.log('Something went wrong while resolving a leafref. Reached a leaf with predicate.');
+ } else {
+ // contains no predicate
+ if (!objectCopy) {
+ break;
+ }
+ results.push(objectCopy[fragment]);
+ }
+ }
+ } else {
+ if (_isArray(objectCopy)) {
+ // is array
+ objectCopy = _map(objectCopy, fragment);
+
+ // If any of the deeper object is an array, flatten the entire list.
+ // This would usually be a bad leafref going out of its scope.
+ // Log it too
+ for (let i = 0; i < objectCopy.length; i++) {
+ if (_isArray(objectCopy[i])) {
+ objectCopy = _flatten(objectCopy);
+ console.log('This might be a bad leafref. Verify with backend team.')
+ break;
+ }
+ }
+ } else {
+ // is object
+ if (fragment.match(/\[.*\]/g)) {
+ // contains a predicate
+ let predicateStr = fragment.match(/\[.*\]/g)[0];
+ // Clip leading [ and trailing ]
+ predicateStr = predicateStr.substr(1, predicateStr.length - 2);
+ const predicateKeyValue = predicateStr.split('=');
+ const predicateKey = predicateKeyValue[0];
+ const predicateValue = predicateKeyValue[1];
+ // get key for object to search into
+ let key = fragment.split('[')[0];
+ let searchObject = {};
+ searchObject[predicateKey] = predicateValue;
+ let found = _find(objectCopy[key], searchObject);
+ if (found) {
+ objectCopy = found;
+ } else {
+ // check for numerical value
+ if (predicateValue != "" &&
+ predicateValue != null &&
+ predicateValue != NaN &&
+ predicateValue != Infinity &&
+ predicateValue != -Infinity) {
+ let numericalPredicateValue = _toNumber(predicateValue);
+ if (_isNumber(numericalPredicateValue)) {
+ searchObject[predicateKey] = numericalPredicateValue;
+ found = _find(objectCopy[key], searchObject);
+ }
+ }
+ if (found) {
+ objectCopy = found;
+ } else {
+ return [];
+ }
+ }
+ } else {
+ // contains no predicate
+ if (!objectCopy) {
+ break;
+ }
+ objectCopy = objectCopy[fragment];
+ if (!objectCopy) {
+ // contains no value
+ break;
+ }
+ }
+ }
+ }
+ i--;
+ fragment = pathArray[pathArray.length - i];
+ }
+
+ return results;
+}
+
+function resolveCurrentPredicate(leafRefPath, container, pathCopy) {
+ if (leafRefPath.indexOf('current()') != -1) {
+ // contains current
+
+ // Get the relative path from current
+ let relativePath = leafRefPath.match("current\\(\\)\/(.*)\]");
+ let relativePathArray = relativePath[1].split('/');
+
+ while (relativePathArray[0] == '..') {
+ pathCopy.pop();
+ relativePathArray.shift();
+ }
+
+ // Supports only one relative path up
+ // To support deeper paths, will need to massage the string more
+ // i.e. change '/'' to '.'
+ const searchPath = pathCopy.join('.').concat('.', relativePathArray[0]);
+ const searchValue = resolvePath(container.model, searchPath);
+
+ const predicateStr = leafRefPath.match("(current.*)\]")[1];
+ leafRefPath = leafRefPath.replace(predicateStr, searchValue);
+ }
+ return leafRefPath;
+}
+
+function cleanupFieldKeyArray (fieldKeyArray) {
+ fieldKeyArray.map((fieldKey, fieldKeyIndex) => {
+ fieldKeyArray[fieldKeyIndex] = fieldKey.replace(/.*\/(.*)/, '$1');
+ });
+ return fieldKeyArray;
+}
+
+export default function resolveLeafRefPath(catalogs, leafRefPath, fieldKey, path, container) {
+ let pathCopy = _clone(path);
+ // Strip any prefixes
+ let leafRefPathCopy = leafRefPath.replace(/[\w\d]*:/g, '');
+ // Strip any spaces
+ leafRefPathCopy = leafRefPathCopy.replace(/\s/g, '');
+
+ // resolve any current paths
+ leafRefPathCopy = resolveCurrentPredicate(leafRefPathCopy, container, pathCopy);
+
+ // Split on delimiter (/)
+ const pathArray = leafRefPathCopy.split('/');
+
+ let fieldKeyArray = fieldKey.split(':');
+
+ // strip prepending qualifiers from fieldKeys
+ fieldKeyArray = cleanupFieldKeyArray(fieldKeyArray);
+ let results = [];
+
+ // Check if relative path or not
+ // TODO: Below works but
+ // better to convert the pathCopy to absolute/rooted path
+ // and use the absolute module instead
+ if (isRelativePath(leafRefPathCopy)) {
+ let i = pathArray.length;
+ while (pathArray[pathArray.length - i] == '..') {
+ fieldKeyArray.splice(-1, 1);
+ if (!isNaN(Number(fieldKeyArray[fieldKeyArray.length - 1]))) {
+ // found a number, so an index. strip it
+ fieldKeyArray.splice(-1, 1);
+ }
+ i--;
+ }
+
+ // traversed all .. - now traverse down
+ if (fieldKeyArray.length == 1) {
+ for (let key in catalogs) {
+ for (let subKey in catalogs[key]) {
+ let found = _find(catalogs[key][subKey], {
+ id: fieldKeyArray[0]
+ });
+ if (found) {
+ results = getAbsoluteResults(found, pathArray.splice(-i, i));
+ return results;
+ }
+ }
+ }
+ } else if (fieldKeyArray.length == 2) {
+ for (let key in catalogs) {
+ for (let subKey in catalogs[key]) {
+ console.log(key, subKey);
+ var found = _find(catalogs[key][subKey], {
+ id: fieldKeyArray[0]
+ });
+ if (found) {
+ for (let foundKey in found) {
+ if (_isArray(found[foundKey])) {
+ let topLevel = _find(found[foundKey], {
+ id: fieldKeyArray[1]
+ });
+ if (topLevel) {
+ results = getAbsoluteResults(topLevel, pathArray.splice(-i, i));
+ return results;
+ }
+ } else {
+ if (foundKey == fieldKeyArray[1]) {
+ results = getAbsoluteResults(found[foundKey], pathArray.splice(-i, i));
+ return results;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (fieldKeyArray.length == 3) {
+ for (let key in catalogs) {
+ for (let subKey in catalogs[key]) {
+ let found = _find(catalogs[key][subKey], {
+ id: fieldKeyArray[0]
+ });
+ if (found) {
+ for (let foundKey in found) {
+ if (_isArray(found[foundKey])) {
+ let topLevel = _find(found[foundKey], {
+ id: fieldKeyArray[1]
+ });
+ if (topLevel) {
+ results = getAbsoluteResults(topLevel, pathArray.splice(-i, i));
+ return results;
+ }
+ } else {
+ if (foundKey == fieldKeyArray[1]) {
+ results = getAbsoluteResults(found[foundKey], pathArray.splice(-i, i));
+ return results;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ // not supported - too many levels deep ... maybe some day
+ console.log('The relative path is from a node too many levels deep from root. This is not supported at the time');
+ }
+ } else {
+ // absolute path
+ if (pathArray[0] == "") {
+ pathArray.splice(0, 1);
+ }
+
+ let catalogKey = pathArray[0];
+ let topLevelObject = {};
+
+ topLevelObject[catalogKey] = catalogs[catalogKey];
+
+ results = getAbsoluteResults(topLevelObject, pathArray);
+
+ return results;
+ }
+}
--- /dev/null
+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);
+
+
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import Alt from 'widgets/skyquake_container/skyquakeAltInstance';
+import yang from '../yang/leaf-utils';
+
+import {
+ schemaActions,
+ schemaSource
+} from 'source/schema'
+import {
+ modelActions,
+ modelSource
+} from 'source/model'
+import adminActions from '../adminActions'
+
+function pullOutWantedProperty(path, result) {
+ if (!result) {
+ return null;
+ }
+ const nodes = path.split('/');
+ if (nodes[0] === 'project') {
+ nodes.shift(); // get rid of top level as it was not sent back
+ result = nodes.reduce((data, node) => data[node], result);
+ }
+ return result;
+}
+
+function findItemIndex(list, key, keyValue) {
+ const keyPath = Array.isArray(keyValue) ? keyValue : key.length > 1 ? JSON.parse(keyValue) : [keyValue];
+ if (key.length > 1) {
+ return list.findIndex(item => {
+ return key.every((k, i) => item[k] === keyPath[i]);
+ });
+ } else {
+ const leaf = key[0];
+ const match = keyPath[0];
+ return list.findIndex(item => {
+ return item[leaf] === match;
+ });
+ }
+}
+
+function getItemFromList(list, key, keyValue) {
+ return list[findItemIndex(list, key, keyValue)];
+}
+
+function getElement(path, store) {
+ return path.reduce((parent, node, index) => {
+ const element = Object.assign({}, parent);
+ if (parent.type === 'list') {
+ element.type = 'list-entry'
+ element.value = getItemFromList(parent.value, parent.schema.key, node);
+ } else {
+ element.schema = parent.schema.properties.find(property => property.name === node)
+ element.type = element.schema.type;
+ element.value = (parent.value && parent.value[node]) || (parent.value[node] = {});
+ }
+ return element;
+ }, {
+ value: store.data,
+ schema: store.schema[store.path],
+ type: store.schema[store.path].type
+ });
+}
+
+function massageData(data, schema){
+ return Object.keys(data).reduce((o, name) => {
+ let input = data[name];
+ let output = null;
+ if (yang.isLeafEmpty(input.property)) {
+ output = {type: 'leaf_empty', data: input.value.length ? 'set' : ''}
+ } else if (yang.isLeafList(input.property)) {
+ const newList = Array.isArray(input.value) ? input.value : input.value.split(',');
+ const oldList = input.currentValue ? (Array.isArray(input.currentValue) ? input.currentValue : input.currentValue.split(',')) : [];
+ const addList = newList.reduce((list, v) => {
+ v = v.trim();
+ if (v) {
+ const i = oldList.indexOf(v);
+ if (i === -1) {
+ list.push(v);
+ } else {
+ oldList.splice(i, 1);
+ }
+ }
+ return list;
+ }, [])
+ output = {type: 'leaf_list', data: {}};
+ addList.length && (output.data.add = addList);
+ oldList.length && (output.data.remove = oldList);
+ } else {
+ output = input.value;
+ }
+ o[name] = output;
+ return o
+ }, {})
+}
+
+class ModelStore {
+ constructor(path) {
+ this.state = {
+ path,
+ isLoading: false
+ };
+ this.bindActions(adminActions);
+ this.registerAsync(modelSource);
+ this.bindActions(modelActions);
+ this.registerAsync(schemaSource);
+ this.bindActions(schemaActions);
+ this.exportPublicMethods({
+ get: this.get,
+ update: this.update,
+ create: this.create,
+ 'delete': this.remove
+ })
+ }
+
+ get = () => {
+ Promise.all([
+ new Promise((resolve, reject) => {
+ this.getInstance().loadSchema(this.state.path)
+ .then(result => resolve(result))
+ .catch(error => reject(error))
+ }),
+ new Promise((resolve, reject) => {
+ const result = this.getInstance().loadModel(this.state.path)
+ .then(result => resolve(pullOutWantedProperty(this.state.path, result)))
+ .catch(error => reject(error))
+ })
+ ]).then((results) => {
+ this.setState({
+ isLoading: false,
+ path: this.state.path,
+ schema: results[0][this.state.path],
+ data: results[1] || {}
+ });
+ }).catch((errors) => {
+ this.setState({
+ isLoading: false,
+ error: {
+ get: errors
+ }
+ })
+ })
+ this.setState({
+ isLoading: true,
+ path: this.state.path
+ })
+ }
+ update = (path, obj) => {
+ const e = getElement(path, this.state);
+ obj = massageData(obj, e.schema);
+ this.getInstance().updateModel(path, obj)
+ .then((response) => {
+ const errors = [];
+ const target = getElement(path, this.state);
+ for (const name in response.result) {
+ if (response.result[name].success) {
+ target.value[name] = response.result[name].value
+ } else {
+ errors.push(response.result[name]);
+ }
+ }
+ this.setState({
+ updatingPath: null,
+ data: this.state.data
+ });
+ errors && this.setState({
+ error: {
+ update: errors
+ }
+ });
+ }).catch((errors) => {
+ this.setState({
+ updatingPath: null,
+ error: {
+ update: errors
+ }
+ })
+ })
+ this.setState({
+ updatingPath: path,
+ })
+ }
+
+ create = (path, obj) => {
+ const e = getElement(path, this.state);
+ obj = massageData(obj, e.schema);
+ this.getInstance().createModel(path, obj)
+ .then((response) => {
+ const createList = () => {
+ const parentPath = path.slice();
+ const name = parentPath.pop();
+ const list = [];
+ const parent = getElement(parentPath, this.state)
+ if (parent.value) parent.value[name] = [];
+ return parent.value[name];
+ }
+ const errors = [];
+ const target = getElement(path, this.state);
+ const list = target.value || createList();
+ list.unshift(response.data);
+ this.setState({
+ updatingPath: null,
+ data: this.state.data
+ });
+ errors && this.setState({
+ error: {
+ update: errors
+ }
+ });
+ }).catch((errors) => {
+ this.setState({
+ updatingPath: null,
+ error: {
+ update: errors
+ }
+ })
+ })
+ this.setState({
+ updatingPath: path,
+ })
+ }
+
+ remove = (path, obj) => {
+ this.getInstance().deleteModel(path)
+ .then((response) => {
+ path = path.slice();
+ const id = path.pop();
+ const list = getElement(path, this.state);
+ const index = findItemIndex(list.value, list.schema.key, id);
+ list.value.splice(index, 1);
+ this.setState({
+ updatingPath: null,
+ data: this.state.data
+ });
+ }).catch((errors) => {
+ this.setState({
+ updatingPath: null,
+ error: {
+ 'delete': errors
+ }
+ })
+ })
+ this.setState({
+ updatingPath: path,
+ })
+ }
+}
+
+export default ModelStore
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+/**
+ * Created by onvelocity on 1/27/16.
+ *
+ * This class provides utility methods for interrogating an instance of model uiState object.
+ */
+
+export default {
+ isString(property = {}) {
+ return (typeof(property['data-type']) == 'string') && (property['data-type'].toLowerCase() == 'string')
+ },
+ isBoolean(property = {}) {
+ return (typeof(property['data-type']) == 'string') && (property['data-type'].toLowerCase() == 'boolean')
+ },
+ isLeafEmpty(property = {}) {
+ return (typeof(property['data-type']) == 'string') && (property['data-type'].toLowerCase() == 'empty')
+ },
+ isLeaf(property = {}) {
+ return property.type === 'leaf';
+ },
+ isLeafOrLeafList(property = {}) {
+ return property.type === 'leaf' || property.type === 'leaf_list';
+ },
+ isLeafList(property = {}) {
+ return property.type === 'leaf_list';
+ },
+ isList(property = {}) {
+ return property.type === 'list';
+ },
+ isLeafRef(property = {}) {
+ const type = property['data-type'] || {};
+ return type.hasOwnProperty('leafref');
+ },
+ isEnumeration(property = {}) {
+ const type = property['data-type'] || {};
+ return type.hasOwnProperty('enumeration');
+ },
+ isRequired(property = {}) {
+ return property.mandatory === 'true';
+ },
+ isKey(property = {}) {
+ return property['is-key'] === 'true';
+ },
+ isJSON(property = {}) {
+ property.name.match(/(vnfd|nsd):meta$/)
+ },
+ getDefaultValue(property = {}) {
+ if (property['default-value']) {
+ return property['default-value'];
+ }
+ return null;
+ },
+ getEnumeration(property = {}, value) {
+ const enumeration = property['data-type'].enumeration.enum;
+ if (typeof enumeration === 'string') {
+ return [{name: enumeration, value: enumeration, isSelected: String(value) === enumeration}];
+ }
+ return Object.keys(enumeration).map(enumName => {
+ let enumValue = enumName;
+ // warn we only support named enums and systematically ignore enum values
+ //const enumObj = enumeration[enumName];
+ //if (enumObj) {
+ // enumValue = enumObj.value || enumName;
+ //}
+ return {name: enumName, value: enumValue, isSelected: String(enumValue) === String(value)};
+ });
+ },
+ getDisplayValue(property = {}, rawValue) {
+ if (this.isLeafEmpty(property) && rawValue !== undefined && rawValue !== null) {
+ return "Enabled";
+ } if (this.isLeafList(property) && Array.isArray(rawValue)) {
+ return rawValue.join(", ");
+ }
+ return rawValue;
+ },
+
+ isValueSet(property = {}, rawValue) {
+ return !(
+ (typeof rawValue === 'undefined')
+ || (rawValue == null)
+ || (typeof rawValue === 'string' && rawValue.length === 0)
+ );
+ }
+}
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+/**
+ * Created by onvelocity on 1/27/16.
+ *
+ * This class provides utility methods for interrogating an instance of model uiState object.
+ */
+
+import _includes from 'lodash/includes'
+import _isArray from 'lodash/isArray'
+import changeCase from 'change-case'
+import utils from '../utils'
+
+export default {
+ isLeaf(property = {}) {
+ return /leaf|leaf_list/.test(property.type);
+ },
+ isList(property = {}) {
+ return property.type === 'list';
+ },
+ isContainer(property = {}) {
+ return property.type === 'container';
+ },
+ isChoice(property = {}) {
+ return property.type === 'choice';
+ },
+ isLeafList(property = {}) {
+ return property.type === 'leaf_list';
+ },
+}
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+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 CompressionPlugin = require("compression-webpack-plugin");
+
+// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
+process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
+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',
+ 'source': path.resolve(frameworkPath) + '/source',
+ '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: /node_modules/,
+ loader: 'babel-loader',
+ query: {
+ presets: ["es2015", "stage-0", "react"]
+ }
+ }, {
+ test: /\.css$/,
+ loader: 'style!css'
+ }, {
+ test: /\.scss/,
+ loader: 'style!css!sass'
+ }
+ ]
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ filename: '../' + htmlFilename,
+ template: frameworkPath + '/plugin-index.html'
+ })
+ ]
+};
+
+if (process.argv.indexOf('--optimize-minimize') !== -1) {
+ // we are going to output a gzip file in the production process
+ config.output.filename = "gzip-" + config.output.filename;
+ config.plugins.push(new webpack.DefinePlugin({ // <-- key to reducing React's size
+ 'process.env': {
+ 'NODE_ENV': JSON.stringify('production')
+ }
+ }));
+ config.plugins.push(new CompressionPlugin({
+ asset: "[path]", // overwrite js file with gz file
+ algorithm: "gzip",
+ test: /\.(js)$/
+ }));
+}
+module.exports = config;
Composer.get = function(req) {
var api_server = req.query['api_server'];
var results = {}
+ var projectPrefix = req.session.projectId ? "project-" : "";
return new Promise(function(resolve, reject) {
Promise.all([
rp({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep'),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
resolveWithFullResponse: true
}),
rp({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd?deep'),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
resolveWithFullResponse: true
}),
rp({
- uri: utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata?deep'),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
// headers: _.extend({},
// constants.HTTP_HEADERS.accept.collection,
// {
- // 'Authorization': req.get('Authorization')
+ // 'Authorization': req.session && req.session.authorization
// }),
// forever: constants.FOREVER_ON,
// rejectUnauthorized: false,
"descriptors": []
}];
if (result[0].body) {
- response[0].descriptors = JSON.parse(result[0].body).collection['nsd:nsd'];
+ response[0].descriptors = utils.dataToJsonSansPropNameNamespace(result[0].body).collection['nsd'];
if (result[2].body) {
- var data = JSON.parse(result[2].body);
- if (data && data["nsr:ns-instance-opdata"] && data["nsr:ns-instance-opdata"]["rw-nsr:nsd-ref-count"]) {
- var nsdRefCountCollection = data["nsr:ns-instance-opdata"]["rw-nsr:nsd-ref-count"];
+ var data = utils.dataToJsonSansPropNameNamespace(result[2].body);
+ if (data && data["nsr:ns-instance-opdata"]) {
response[0].descriptors.map(function(nsd) {
if (!nsd["meta"]) {
nsd["meta"] = {};
if (typeof nsd['meta'] == 'string') {
nsd['meta'] = JSON.parse(nsd['meta']);
}
- nsd["meta"]["instance-ref-count"] = _.findWhere(nsdRefCountCollection, {
- "nsd-id-ref": nsd.id
- })["instance-ref-count"];
});
}
}
};
if (result[1].body) {
- response[1].descriptors = JSON.parse(result[1].body).collection['vnfd:vnfd'];
+ response[1].descriptors = utils.dataToJsonSansPropNameNamespace(result[1].body).collection['vnfd'];
};
- // if (result[2].body) {
- // response[2].descriptors = JSON.parse(result[2].body).collection['pnfd:pnfd'];
- // };
resolve({
statusCode: response.statusCode || 200,
data: JSON.stringify(response)
console.log('Deleting', catalogType, id, 'from', api_server);
return new Promise(function(resolve, reject) {
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog/' + catalogType + '/' + id,
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog/' + catalogType + '/' + encodeURIComponent(id)),
method: 'DELETE',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
Composer.getVNFD = function(req) {
var api_server = req.query['api_server'];
var vnfdID = req.body.data;
- var authorization = req.get('Authorization');
+ var authorization = req.session && req.session.authorization;
var VNFDs = [];
if (typeof(vnfdID) == "object" && vnfdID.constructor.name == "Array") {
vnfdID.map(function(id) {
function requestVNFD(id) {
return new Promise(function(resolve, reject) {
- var url = utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd' + (id ? '/' + id : '') + '?deep';
+ var url = utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd' + (id ? '/' + encodeURIComponent(id) : '') + '?deep';
request({
- uri: url,
+ uri: utils.projectContextUrl(req, url),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
'Authorization': authorization
return new Promise(function(resolve, reject) {
var requestHeaders = {};
_.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- uri: utils.confdPort(api_server) + '/api/config/' + catalogType + '-catalog',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/' + catalogType + '-catalog'),
method: 'POST',
headers: requestHeaders,
forever: constants.FOREVER_ON,
return new Promise(function(resolve, reject) {
var requestHeaders = {};
_.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog' + '/' + catalogType + '/' + id,
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog' + '/' + catalogType + '/' + encodeURIComponent(id)),
method: 'PUT',
headers: requestHeaders,
forever: constants.FOREVER_ON,
download_host = req.protocol + '://' + req.get('host');//req.api_server + ':' + utils.getPortForProtocol(req.protocol);
}
+ var input = {
+ 'external-url': download_host + '/composer/' + constants.BASE_PACKAGE_UPLOAD_DESTINATION + '/' + req.file.filename,
+ 'package-type': 'VNFD',
+ 'package-id': uuid()
+ }
+
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-create');
+
+ input = utils.addProjectContextToRPCPayload(req, uri, input);
+
+ var authorization = {};
+ if (req.session && req.session.authorization) {
+ authorization = {
+ 'Authorization': req.session && req.session.authorization
+ };
+ } else {
+ // For RIFT-16894: onboard_pkg script | Upload packages through command line
+ authorization = {
+ 'Authorization': req.get('Authorization')
+ };
+ }
+
return new Promise(function(resolve, reject) {
Promise.all([
rp({
- uri: utils.confdPort(api_server) + '/api/operations/package-create',
+ uri: uri,
method: 'POST',
- headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
- }),
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, authorization),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
resolveWithFullResponse: true,
json: true,
body: {
- input: {
- 'external-url': download_host + '/composer/upload/' + req.file.filename,
- 'package-type': 'VNFD',
- 'package-id': uuid()
- }
+ input: input
}
})
]).then(function(result) {
data['transaction_id'] = result[0].body['output']['transaction-id'];
// Add a status checker on the transaction and then to delete the file later
- PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id']);
+ PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id'], 'create');
// Return status to composer UI to update the status.
resolve({
download_host = req.protocol + '://' + req.get('host');//api_server + ':' + utils.getPortForProtocol(req.protocol);
}
var input = {
- 'external-url': download_host + '/composer/update/' + req.file.filename,
+ 'external-url': download_host + '/composer/' + constants.BASE_PACKAGE_UPLOAD_DESTINATION + '/' + req.file.filename,
'package-type': 'VNFD',
'package-id': uuid()
- }
+ };
+
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-update');
+
+ input = utils.addProjectContextToRPCPayload(req, uri, input);
+
return new Promise(function(resolve, reject) {
Promise.all([
rp({
- uri: utils.confdPort(api_server) + '/api/operations/package-update',
+ uri: uri,
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
data['transaction_id'] = result[0].body['output']['transaction-id'];
// Add a status checker on the transaction and then to delete the file later
- PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id'], true);
+ PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id'], 'update');
// Return status to composer UI to update the status.
resolve({
PackageManager.export = function(req) {
// /api/operations/package-export
var api_server = req.query['api_server'];
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-export');
+ var input = req.body;
+ input = utils.addProjectContextToRPCPayload(req, uri, input);
return new Promise(function(resolve, reject) {
Promise.all([
rp({
- uri: utils.confdPort(api_server) + '/api/operations/package-export',
+ uri: uri,
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
resolveWithFullResponse: true,
json: true,
- body: { "input": req.body}
+ body: { "input": input }
})
]).then(function(result) {
var data = {};
PackageManager.copy = function(req) {
// /api/operations/package-copy
var api_server = req.query['api_server'];
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-copy');
+ var input = req.body;
+ input = utils.addProjectContextToRPCPayload(req, uri, input);
+
return new Promise(function(resolve, reject) {
Promise.all([
rp({
- uri: utils.confdPort(api_server) + '/api/operations/package-copy',
+ uri: uri,
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
resolveWithFullResponse: true,
json: true,
- body: { "input": req.body}
+ body: { "input": input}
})
]).then(function(result) {
var data = {};
}
/**
- * This methods retrieves the status of package operations. It takes an optional
+ * These methods retrieves the status of package operations. It takes an optional
* transaction id (id) this if present will return only that status otherwise
* an array of status' will be response.
*/
-PackageManager.getJobStatus = function(req) {
+PackageManager.getJobStatus = function(req, jobType) {
var api_server = req.query["api_server"];
var uri = utils.confdPort(api_server);
var id = req.params['id'];
- var url = uri + '/api/operational/copy-jobs' + (id ? '/job/' + id : '');
+ var url = utils.projectContextUrl(req, uri + '/api/operational/' + jobType + (id ? '/job/' + id : ''));
return new Promise(function(resolve, reject) {
request({
url: url,
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false
}, function(error, response, body) {
- if (utils.validateResponse('restconfAPI.streams', error, response, body, resolve, reject)) {
+ if (utils.validateResponse('PackageManager.getJobStatus', error, response, body, resolve, reject)) {
var returnData;
+ var jsonResponse = JSON.parse(response.body);
if (id) {
- returnData = JSON.parse(response.body)['rw-pkg-mgmt:job'];
+ returnData = jsonResponse['rw-pkg-mgmt:job'];
+ if (!returnData){
+ returnData = {
+ status: 'failure',
+ errors: [{value: "Internal error"}]
+ }
+ }
} else {
- var data = JSON.parse(response.body)['rw-pkg-mgmt:copy-jobs'];
+ var data = jsonResponse['rw-pkg-mgmt:' + jobType];
returnData = (data && data.job) || [];
}
resolve({
statusCode: response.statusCode,
data: returnData
- })
- };
+ });
+ }
})
})
}
+PackageManager.getCopyJobStatus = function(req) {
+ return PackageManager.getJobStatus(req, 'copy-jobs');
+}
+PackageManager.getImportJobStatus = function(req) {
+ return PackageManager.getJobStatus(req, 'create-jobs');
+}
+PackageManager.getUpdateJobStatus = function(req) {
+ return PackageManager.getJobStatus(req, 'update-jobs');
+}
function makeAssetTypeParamName (type) {
return type.toLowerCase() + '-file-type';
var package_id = req.query['package_id'];
var package_type = req.query['package_type'].toUpperCase();
var package_path = req.query['package_path'];
- if (!download_host) {
+ if (!download_host) {
download_host = req.protocol + '://' + req.get('host');//api_server + ':' + utils.getPortForProtocol(req.protocol);
}
+ var filename = req.file.filename;
+ var assetName = req.file.originalname;
var input = {
- 'external-url': download_host + '/composer/upload/' + req.query['package_id'] + '/' + req.file.filename,
+ 'external-url': download_host + '/composer/' + constants.BASE_PACKAGE_UPLOAD_DESTINATION + '/' + filename,
'package-type': package_type,
'package-id': package_id,
- 'package-path': package_path ? package_path + '/' + req.file.filename : req.file.filename
- }
+ 'package-path': package_path ? package_path + '/' + assetName : assetName
+ };
+
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-file-add');
+
+ input = utils.addProjectContextToRPCPayload(req, uri, input);
+
var assetType = req.query['asset_type'].toUpperCase();
input[makeAssetTypeParamName(package_type)] = assetType;
return new Promise(function(resolve, reject) {
Promise.all([
rp({
- uri: utils.confdPort(api_server) + '/api/operations/package-file-add',
+ uri: uri,
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
]).then(function(result) {
var data = {};
data['transaction_id'] = result[0].body['output']['task-id'];
+ // Add a status checker on the transaction and then to delete the file later
+ PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id'], 'download');
resolve({
statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
data: data
FileManager.get = function(req) {
var api_server = req.query['api_server'];
var type = req.query['package_type'] && req.query['package_type'].toUpperCase();
- var id = req.query['package_id'];
+ var packageId = req.query['package_id'];
var downloadUrl = req.query['url'];
var path = req.query['package_path'];
var assetType = req.query['asset_type'];
var input = {
"package-type": type,
- "package-id": id
+ "package-id": packageId
}
var payload = {input: input};
if(req.method == 'GET') {
}
function deleteFile(payload) {
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/rw-pkg-mgmt:package-file-delete');
+ payload.input = utils.addProjectContextToRPCPayload(req, uri, payload.input);
return new Promise(function(resolve, reject) {
rp({
- uri: utils.confdPort(api_server) + '/api/operations/rw-pkg-mgmt:package-file-delete',
+ uri: uri,
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
json: payload,
forever: constants.FOREVER_ON,
})
}
function download(payload) {
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/rw-pkg-mgmt:package-file-add');
+ payload.input = utils.addProjectContextToRPCPayload(req, uri, payload.input);
return new Promise(function(resolve, reject) {
rp({
- uri: utils.confdPort(api_server) + '/api/operations/rw-pkg-mgmt:package-file-add',
+ uri: uri,
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
json: payload,
forever: constants.FOREVER_ON,
})
}
function list(payload) {
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/get-package-endpoint');
+ payload.input = utils.addProjectContextToRPCPayload(req, uri, payload.input);
return new Promise(function(resolve, reject) {
rp({
- uri: utils.confdPort(api_server) + '/api/operations/get-package-endpoint',
+ uri: uri,
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
json: payload,
forever: constants.FOREVER_ON,
}
parsedEndpoint = URL.parse(endpoint);
rp({
- uri: api_server + ':' + parsedEndpoint.port + parsedEndpoint.path,
+ uri: utils.projectContextUrl(req, api_server + ':' + parsedEndpoint.port + parsedEndpoint.path),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
}
}).catch(function(err) {
console.log('Failed to retrieve FileManager.list')
- resolve(err);
+ reject({
+ statusCode: constants.HTTP_RESPONSE_CODES.ERROR.INTERNAL_SERVER_ERROR,
+ error: {
+ errorMessage: JSON.stringify(err)
+ }
+ });
})
}
})
var id = req.params['id'];
return new Promise(function(resolve, reject) {
request({
- url: uri + url + '?deep',
+ url: utils.projectContextUrl(req, uri + url + '?deep'),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
}, function(error, response, body) {
- if (utils.validateResponse('restconfAPI.streams', error, response, body, resolve, reject)) {
+ if (utils.validateResponse('FileManager.job', error, response, body, resolve, reject)) {
var data = JSON.parse(response.body)['rw-pkg-mgmt:download-jobs'];
var returnData = [];
data && data.job.map(function(d) {
var fs = require('fs');
var _ = require('lodash');
-PackageFileHandler = {};
+var PackageFileHandler = {};
function deleteFile(filename) {
setTimeout(function() {
- fs.unlinkSync(constants.BASE_PACKAGE_UPLOAD_DESTINATION + filename);
+ try {
+ fs.unlinkSync(constants.BASE_PACKAGE_UPLOAD_DESTINATION + '/' + filename);
+ } catch (e) {
+ console.log("file delete error", e);
+ }
}, constants.PACKAGE_FILE_DELETE_DELAY_MILLISECONDS);
};
-function checkStatus(req, transactionId, isUpdate) {
- var upload_server = req.query['upload_server'];
- var headers = _.extend({},
- {
- 'Authorization': req.get('Authorization')
- }
- );
- var type = isUpdate ? 'update' : 'upload';
+function checkStatus(req, transactionId, jobType) {
+ var api_server = req.query["api_server"];
+ var uri = utils.confdPort(api_server);
+ var id = req.params['id'];
+ var jobName = jobType + '-jobs';
+ var url = utils.projectContextUrl(req, uri + '/api/operational/' + jobName + (transactionId ? '/job/' + transactionId : ''));
request({
- url: upload_server + ':' + constants.PACKAGE_MANAGER_SERVER_PORT + '/api/' + type + '/' + transactionId + '/state',
- type: 'GET',
- headers: headers,
+ url: url,
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }),
forever: constants.FOREVER_ON,
rejectUnauthorized: false
}, function(error, response, body) {
} else {
var jsonStatus = null;
if (typeof body == 'string' || body instanceof String) {
- jsonStatus = JSON.parse(body);
+ try {jsonStatus = JSON.parse(body)['rw-pkg-mgmt:job'];} catch(e) {jsonStatus = {status: 'failure'}}
} else {
jsonStatus = body;
}
- if (jsonStatus.status && (jsonStatus.status == 'success' || jsonStatus.status == 'failure')) {
+ if (jsonStatus && jsonStatus.status && (jsonStatus.status == 'COMPLETED' || jsonStatus.status == 'FAILED')) {
console.log('Transaction ', transactionId, ' completed with status ', jsonStatus.status ,'. Will delete file', req.file.filename, ' in ', constants.PACKAGE_FILE_DELETE_DELAY_MILLISECONDS ,' milliseconds');
deleteFile(req.file.filename);
} else {
setTimeout(function() {
- checkStatus(req, transactionId, isUpdate);
+ checkStatus(req, transactionId, jobType);
}, constants.PACKAGE_FILE_ONBOARD_TRANSACTION_STATUS_CHECK_DELAY_MILLISECONDS);
}
}
});
};
-PackageFileHandler.checkCreatePackageStatusAndHandleFile = function(req, transactionId, isUpdate) {
- checkStatus(req, transactionId, isUpdate);
+PackageFileHandler.checkCreatePackageStatusAndHandleFile = function(req, transactionId, jobType) {
+ checkStatus(req, transactionId, jobType);
};
"name": "Catalog",
"dashboard" : "./src/components/ComposerApp.js",
"order": 2,
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-project:project-admin",
+ "rw-project:project-oper",
+ "rw-project-mano:catalog-oper",
+ "rw-project-mano:catalog-admin"],
"routes" : [{
"label": "Catalog",
"route": "/",
"react": "^0.14.7",
"react-addons-css-transition-group": "^0.14.7",
"react-addons-pure-render-mixin": "^0.14.7",
+ "react-addons-shallow-compare": "^0.14.7",
"react-crouton": "^0.2.7",
"react-dom": "^0.14.3",
"react-popout": "^0.4.0",
"react-treeview": "0.4.2",
"request-promise": "^3.0.0",
"require-json": "0.0.1",
- "uuid": "^3.0.0"
+ "uuid": "^3.0.0",
+ "validator": "^7.0.0"
},
"devDependencies": {
"babel-core": "^6.4.5",
"grunt-contrib-clean": "^0.7.0",
"grunt-contrib-connect": "^0.11.2",
"grunt-contrib-copy": "^0.8.2",
- "grunt-karma": "^0.12.1",
"grunt-open": "^0.2.3",
"grunt-version": "^1.0.0",
"grunt-webpack": "^1.0.11",
"jasmine-core": "^2.4.1",
"json-loader": "^0.5.4",
"json2yaml": "^1.1.0",
- "karma": "^0.13.15",
- "karma-chrome-launcher": "^0.2.2",
- "karma-es6-shim": "^0.2.3",
- "karma-jasmine": "^0.3.6",
- "karma-phantomjs-launcher": "^0.2.1",
- "karma-phantomjs-shim": "^1.2.0",
- "karma-script-launcher": "^0.1.0",
- "karma-sourcemap-loader": "^0.3.7",
- "karma-webpack": "^1.7.0",
"load-grunt-tasks": "^3.3.0",
"node-sass": "^3.4.2",
"npm": "^3.7.1",
- "phantomjs": "^1.9.19",
"react-addons-test-utils": "^0.14.7",
"react-hot-loader": "^1.3.0",
"react-inlinesvg": "^0.5.3",
+ "react-open-iconic-svg": "^1.0.4",
"sass-loader": "^3.1.2",
"style-loader": "^0.13.0",
"url-loader": "^0.5.7",
// destination: 'upload/packages/',
destination: function(req, file, cb) {
var dir = constants.BASE_PACKAGE_UPLOAD_DESTINATION;
- if (req.query['package_id']) {
- dir += req.query['package_id'] + '/';
- }
if (!fs.existsSync(dir)){
- mkdirp(dir, function(err) {
+ mkdirp(dir, function(err) {
if (err) {
console.log('Error creating folder for uploads. All systems FAIL!');
throw err;
}
- cb(null, dir);
});
- } else {
- cb(null, dir);
}
+ cb(null, dir);
},
filename: function (req, file, cb) {
- if (req.query['package_id']) {
- cb(null, file.originalname);
- } else {
- cb(null, Date.now() + '_' + file.fieldname + '_' + file.originalname);
- }
+ cb(null, Date.now() + '_' + file.fieldname + '_' + file.originalname);
},
// limits: {
// fieldNameSize: 100,
});
});
-router.post('/api/file-manager', cors(), upload.single('package'), function (req, res, next) {
+router.post('/api/file-manager', cors(), upload.single('file'), function (req, res, next) {
FileManager.addFile(req).then(function(data) {
utils.sendSuccessResponse(data, res);
}, function(error) {
utils.sendErrorResponse(error, res);
});
});
-router.use('/upload', cors(), express.static('upload/packages'));
+router.use('/upload', cors(), function(req, res, next) {
+ console.log('Received request for ', req.originalUrl, ' from ', req.ip);
+ next();
+}, express.static(constants.BASE_PACKAGE_UPLOAD_DESTINATION));
router.post('/update', cors(), upload.single('package'), function (req, res, next) {
PackageManager.update(req).then(function(data) {
utils.sendErrorResponse(error, res);
});
});
-router.get('/api/package-manager/jobs/:id', cors(), function (req, res, next) {
- PackageManager.getJobStatus(req).then(function(data) {
+router.get('/api/package-copy/jobs/:id', cors(), function (req, res, next) {
+ PackageManager.getCopyJobStatus(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+router.get('/api/package-import/jobs/:id', cors(), function (req, res, next) {
+ PackageManager.getImportJobStatus(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+router.get('/api/package-update/jobs/:id', cors(), function (req, res, next) {
+ PackageManager.getUpdateJobStatus(req).then(function(data) {
utils.sendSuccessResponse(data, res);
}, function(error) {
utils.sendErrorResponse(error, res);
PLUGIN_NAME=composer
# 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 --optimize-minimize --progress --config webpack.production.config.js
-echo 'Packaging '$PLUGIN_NAME' using webpack... done'
-echo 'Building plugin '$PLUGIN_NAME'... done'
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
-// /*
-// *
-// * 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.
-// *
-// */
-
-// 'use strict';
-
-// // the models to be transformed into the output DSL JSON meta file
-// var yang = [require('./json-nsd.json'), require('./json-vnfd.json')];
-
-// var _ = require('lodash');
-// var inet = require('./ietf-inet-types.yang.json');
-
-// var utils = {
-// resolvePath(obj, path) {
-// // supports a.b, a[1] and foo[bar], etc.
-// // where obj is ['nope', 'yes', {a: {b: 1}, foo: 2}]
-// // then [1] returns 'yes'; [2].a.b returns 1; [2].a[foo] returns 2;
-// path = path.split(/[\.\[\]]/).filter(d => d);
-// return path.reduce((r, p) => {
-// if (r) {
-// return r[p];
-// }
-// }, obj);
-// },
-// assignPathValue(obj, path, value) {
-// path = path.split(/[\.\[\]]/).filter(d => d);
-// // enable look-ahead to determine if type is array or object
-// const pathCopy = path.slice();
-// // last item in path used to assign value on the resolved object
-// const name = path.pop();
-// const resolvedObj = path.reduce((r, p, i) => {
-// if (typeof(r[p]) !== 'object') {
-// // look-ahead to see if next path item is a number
-// const isArray = !isNaN(parseInt(pathCopy[i + 1], 10));
-// r[p] = isArray ? [] : {}
-// }
-// return r[p];
-// }, obj);
-// resolvedObj[name] = value;
-// }
-// };
-
-// var isType = d => /^(leaf|leaf-list|list|container|choice|case|uses)$/.test(d);
-
-// function deriveCardinalityFromProperty(property, typeName) {
-// if (String(property.mandatory) === 'true') {
-// return '1';
-// }
-// let min = 0, max = Infinity;
-// if (property.hasOwnProperty('min-elements')) {
-// min = parseInt(property['min-elements'], 10) || 0;
-// }
-// if (property.hasOwnProperty('max-elements')) {
-// max = parseInt(property['max-elements'], 10) || Infinity;
-// } else {
-// if (!/^(list|leaf-list)$/.test(typeName)) {
-// max = '1';
-// }
-// }
-// if (min > max) {
-// return String(min);
-// }
-// if (min === max) {
-// return String(min);
-// }
-// return String(min) + '..' + (max === Infinity ? 'N' : max);
-// }
-
-// function cleanWhitespace(text) {
-// if (typeof text === 'string') {
-// return text.replace(/\s+/g, ' ');
-// }
-// return text;
-// }
-
-// function buildProperties(typeData, typeName) {
-// var properties = [];
-// Object.keys(typeData).forEach(name => {
-// var property = typeData[name];
-// var listKey = typeName === 'list' ? String(property.key).split(/\s/).filter(k => k && k !== 'undefined') : false;
-// var meta = {
-// name: name,
-// type: typeName,
-// description: cleanWhitespace(property.description),
-// cardinality: deriveCardinalityFromProperty(property, typeName),
-// 'data-type': property.type,
-// properties: Object.keys(property).filter(isType).reduce((r, childType) => {
-// return r.concat(buildProperties(property[childType], childType));
-// }, [])
-// };
-// if (listKey) {
-// meta.key = listKey;
-// }
-// properties.push(meta);
-// });
-// return properties;
-// }
-
-// function lookupUses(uses, yang) {
-// function doLookup(lookupTypeName) {
-// var key;
-// // warn: hardcoded prefix support for mano-types - other prefixes will be ignored
-// if (/^manotypes:/.test(lookupTypeName)) {
-// var moduleName = lookupTypeName.split(':')[1];
-// key = ['dependencies.mano-types.module.mano-types.grouping', moduleName].join('.');
-// } else {
-// var name = yang.name.replace(/^rw-/, '');
-// key = ['dependencies', name, 'module', name, 'grouping', lookupTypeName].join('.');
-// }
-// return utils.resolvePath(yang, key);
-// }
-// if (typeof uses === 'object') {
-// return Object.keys(uses).reduce((result, key) => {
-// var found = doLookup(key);
-// Object.keys(found).filter(isType).forEach(type => {
-// var property = result[type] || (result[type] = {});
-// Object.assign(property, found[type]);
-// });
-// return result;
-// }, {});
-// } else if (typeof uses === 'string') {
-// return doLookup(uses);
-// }
-// return {};
-// }
-
-// function lookupTypedef(property, yang) {
-// var key;
-// var lookupTypeName = property.type;
-// // warn: hardcoded prefix support - other prefixes will be ignored
-// if (/^manotypes:/.test(lookupTypeName)) {
-// var lookupName = lookupTypeName.split(':')[1];
-// key = ['dependencies.mano-types.module.mano-types.typedef', lookupName].join('.');
-// } else if (/^inet:/.test(lookupTypeName)) {
-// var lookupName = lookupTypeName.split(':')[1];
-// yang = inet;
-// key = ['schema.module.ietf-inet-types.typedef', lookupName].join('.');
-// }
-// if (key) {
-// return utils.resolvePath(yang, key);
-// }
-// }
-
-// function resolveUses(property, yang) {
-// var childData = property.uses;
-// var resolved = lookupUses(childData, yang);
-// //console.log('uses', childData, 'found', resolved);
-// Object.keys(resolved).forEach(type => {
-// var parentTypes = property[type] || (property[type] = {});
-// // copy types into the parent types bucket
-// Object.assign(parentTypes, resolveReferences(yang, resolved[type]));
-// });
-// delete property.uses;
-// }
-
-// function resolveTypedef(property, yang) {
-// if (/:/.test(property.type)) {
-// var found = lookupTypedef(property, yang);
-// if (found) {
-// Object.assign(property, found);
-// }
-// }
-// }
-
-// function resolveReferences(yang, data) {
-// var dataClone = _.cloneDeep(data);
-// function doResolve(typeData) {
-// Object.keys(typeData).forEach(name => {
-// var property = typeData[name];
-// resolveTypedef(property, yang);
-// Object.keys(property).filter(isType).forEach(childType => {
-// if (childType === 'uses') {
-// resolveUses(property, yang);
-// } else {
-// doResolve(property[childType]);
-// }
-// });
-// });
-// }
-// doResolve(dataClone);
-// return dataClone;
-// }
-
-// function module(yang) {
-// let module;
-// var name = yang.name.replace(/^rw-/, '');
-// if (!name) {
-// throw 'no name given in json yang';
-// }
-// const path = ['container', name + '-catalog'].join('.');
-// module = utils.resolvePath(yang, path);
-
-// if (!module) {
-// module = utils.resolvePath(yang, ['schema', 'module', name, path].join('.'));
-// }
-// if (!module) {
-// module = utils.resolvePath(yang, ['dependencies', name, 'module', name, path].join('.'));
-// }
-// if (!module) {
-// throw 'cannot find the module' + name;
-// }
-
-// // module/agument/nsd:nsd-catalog/nsd:nsd/meta
-// const augLeafPath = ['schema.module', 'rw-' + name, 'augment', '/' + name + ':' + name + '-catalog/' + name + ':' + name, 'leaf'];
-// const meta = utils.resolvePath(yang, augLeafPath.concat('meta').join('.'));
-
-// const putLeafPath = ['dependencies', name, 'module', name, path, 'list', name, 'leaf'];
-
-// if (meta) {
-// utils.assignPathValue(yang, putLeafPath.concat(['meta']).join('.'), meta);
-// }
-
-// // module/agument/nsd:nsd-catalog/nsd:nsd/logo
-// const logo = utils.resolvePath(yang, augLeafPath.concat('logo').join('.'));
-// if (logo) {
-// utils.assignPathValue(yang, putLeafPath.concat(['logo']).join('.'), logo);
-// }
-// var data = module.list;
-
-// return {name: name, data: resolveReferences(yang, data)};
-
-// }
-
-// function reduceModule(result, module) {
-// result[module.name] = buildProperties(module.data, 'list')[0];
-// return result;
-// }
-
-// var result = yang.map(module).reduce(reduceModule, {});
-
-// console.log(JSON.stringify(result, null, 5));
+//: /*
+//: *
+//: * 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.
+//: *
+//: */
+//:
+//: 'use strict';
+//:
+//: // the models to be transformed into the output DSL JSON meta file
+//: var yang = [require('./json-nsd.json'), require('./json-vnfd.json')];
+//:
+//: var _ = require('lodash');
+//: var inet = require('./ietf-inet-types.yang.json');
+//:
+//: var utils = {
+//: resolvePath(obj, path) {
+//: // supports a.b, a[1] and foo[bar], etc.
+//: // where obj is ['nope', 'yes', {a: {b: 1}, foo: 2}]
+//: // then [1] returns 'yes'; [2].a.b returns 1; [2].a[foo] returns 2;
+//: path = path.split(/[\.\[\]]/).filter(d => d);
+//: return path.reduce((r, p) => {
+//: if (r) {
+//: return r[p];
+//: }
+//: }, obj);
+//: },
+//: assignPathValue(obj, path, value) {
+//: path = path.split(/[\.\[\]]/).filter(d => d);
+//: // enable look-ahead to determine if type is array or object
+//: const pathCopy = path.slice();
+//: // last item in path used to assign value on the resolved object
+//: const name = path.pop();
+//: const resolvedObj = path.reduce((r, p, i) => {
+//: if (typeof(r[p]) !== 'object') {
+//: // look-ahead to see if next path item is a number
+//: const isArray = !isNaN(parseInt(pathCopy[i + 1], 10));
+//: r[p] = isArray ? [] : {}
+//: }
+//: return r[p];
+//: }, obj);
+//: resolvedObj[name] = value;
+//: }
+//: };
+//:
+//: var isType = d => /^(leaf|leaf-list|list|container|choice|case|uses)$/.test(d);
+//:
+//: function deriveCardinalityFromProperty(property, typeName) {
+//: if (String(property.mandatory) === 'true') {
+//: return '1';
+//: }
+//: let min = 0, max = Infinity;
+//: if (property.hasOwnProperty('min-elements')) {
+//: min = parseInt(property['min-elements'], 10) || 0;
+//: }
+//: if (property.hasOwnProperty('max-elements')) {
+//: max = parseInt(property['max-elements'], 10) || Infinity;
+//: } else {
+//: if (!/^(list|leaf-list)$/.test(typeName)) {
+//: max = '1';
+//: }
+//: }
+//: if (min > max) {
+//: return String(min);
+//: }
+//: if (min === max) {
+//: return String(min);
+//: }
+//: return String(min) + '..' + (max === Infinity ? 'N' : max);
+//: }
+//:
+//: function cleanWhitespace(text) {
+//: if (typeof text === 'string') {
+//: return text.replace(/\s+/g, ' ');
+//: }
+//: return text;
+//: }
+//:
+//: function buildProperties(typeData, typeName) {
+//: var properties = [];
+//: Object.keys(typeData).forEach(name => {
+//: var property = typeData[name];
+//: var listKey = typeName === 'list' ? String(property.key).split(/\s/).filter(k => k && k !== 'undefined') : false;
+//: var meta = {
+//: name: name,
+//: type: typeName,
+//: description: cleanWhitespace(property.description),
+//: cardinality: deriveCardinalityFromProperty(property, typeName),
+//: 'data-type': property.type,
+//: properties: Object.keys(property).filter(isType).reduce((r, childType) => {
+//: return r.concat(buildProperties(property[childType], childType));
+//: }, [])
+//: };
+//: if (listKey) {
+//: meta.key = listKey;
+//: }
+//: properties.push(meta);
+//: });
+//: return properties;
+//: }
+//:
+//: function lookupUses(uses, yang) {
+//: function doLookup(lookupTypeName) {
+//: var key;
+//: // warn: hardcoded prefix support for mano-types - other prefixes will be ignored
+//: if (/^manotypes:/.test(lookupTypeName)) {
+//: var moduleName = lookupTypeName.split(':')[1];
+//: key = ['dependencies.mano-types.module.mano-types.grouping', moduleName].join('.');
+//: } else {
+//: var name = yang.name.replace(/^rw-/, '');
+//: key = ['dependencies', name, 'module', name, 'grouping', lookupTypeName].join('.');
+//: }
+//: return utils.resolvePath(yang, key);
+//: }
+//: if (typeof uses === 'object') {
+//: return Object.keys(uses).reduce((result, key) => {
+//: var found = doLookup(key);
+//: Object.keys(found).filter(isType).forEach(type => {
+//: var property = result[type] || (result[type] = {});
+//: Object.assign(property, found[type]);
+//: });
+//: return result;
+//: }, {});
+//: } else if (typeof uses === 'string') {
+//: return doLookup(uses);
+//: }
+//: return {};
+//: }
+//:
+//: function lookupTypedef(property, yang) {
+//: var key;
+//: var lookupTypeName = property.type;
+//: // warn: hardcoded prefix support - other prefixes will be ignored
+//: if (/^manotypes:/.test(lookupTypeName)) {
+//: var lookupName = lookupTypeName.split(':')[1];
+//: key = ['dependencies.mano-types.module.mano-types.typedef', lookupName].join('.');
+//: } else if (/^inet:/.test(lookupTypeName)) {
+//: var lookupName = lookupTypeName.split(':')[1];
+//: yang = inet;
+//: key = ['schema.module.ietf-inet-types.typedef', lookupName].join('.');
+//: }
+//: if (key) {
+//: return utils.resolvePath(yang, key);
+//: }
+//: }
+//:
+//: function resolveUses(property, yang) {
+//: var childData = property.uses;
+//: var resolved = lookupUses(childData, yang);
+//: //console.log('uses', childData, 'found', resolved);
+//: Object.keys(resolved).forEach(type => {
+//: var parentTypes = property[type] || (property[type] = {});
+//: // copy types into the parent types bucket
+//: Object.assign(parentTypes, resolveReferences(yang, resolved[type]));
+//: });
+//: delete property.uses;
+//: }
+//:
+//: function resolveTypedef(property, yang) {
+//: if (/:/.test(property.type)) {
+//: var found = lookupTypedef(property, yang);
+//: if (found) {
+//: Object.assign(property, found);
+//: }
+//: }
+//: }
+//:
+//: function resolveReferences(yang, data) {
+//: var dataClone = _.cloneDeep(data);
+//: function doResolve(typeData) {
+//: Object.keys(typeData).forEach(name => {
+//: var property = typeData[name];
+//: resolveTypedef(property, yang);
+//: Object.keys(property).filter(isType).forEach(childType => {
+//: if (childType === 'uses') {
+//: resolveUses(property, yang);
+//: } else {
+//: doResolve(property[childType]);
+//: }
+//: });
+//: });
+//: }
+//: doResolve(dataClone);
+//: return dataClone;
+//: }
+//:
+//: function module(yang) {
+//: let module;
+//: var name = yang.name.replace(/^rw-/, '');
+//: if (!name) {
+//: throw 'no name given in json yang';
+//: }
+//: const path = ['container', name + '-catalog'].join('.');
+//: module = utils.resolvePath(yang, path);
+//:
+//: if (!module) {
+//: module = utils.resolvePath(yang, ['schema', 'module', name, path].join('.'));
+//: }
+//: if (!module) {
+//: module = utils.resolvePath(yang, ['dependencies', name, 'module', name, path].join('.'));
+//: }
+//: if (!module) {
+//: throw 'cannot find the module' + name;
+//: }
+//:
+//: // module/agument/nsd:nsd-catalog/nsd:nsd/meta
+//: const augLeafPath = ['schema.module', 'rw-' + name, 'augment', '/' + name + ':' + name + '-catalog/' + name + ':' + name, 'leaf'];
+//: const meta = utils.resolvePath(yang, augLeafPath.concat('meta').join('.'));
+//:
+//: const putLeafPath = ['dependencies', name, 'module', name, path, 'list', name, 'leaf'];
+//:
+//: if (meta) {
+//: utils.assignPathValue(yang, putLeafPath.concat(['meta']).join('.'), meta);
+//: }
+//:
+//: // module/agument/nsd:nsd-catalog/nsd:nsd/logo
+//: const logo = utils.resolvePath(yang, augLeafPath.concat('logo').join('.'));
+//: if (logo) {
+//: utils.assignPathValue(yang, putLeafPath.concat(['logo']).join('.'), logo);
+//: }
+//: var data = module.list;
+//:
+//: return {name: name, data: resolveReferences(yang, data)};
+//:
+//: }
+//:
+//: function reduceModule(result, module) {
+//: result[module.name] = buildProperties(module.data, 'list')[0];
+//: return result;
+//: }
+//:
+//: var result = yang.map(module).reduce(reduceModule, {});
+//:
+//: console.log(JSON.stringify(result, null, 5));
type string;
}
- leaf polling_interval_secs {
+ leaf polling-interval-secs {
description "The HTTP polling interval in seconds";
type uint8;
default 2;
class CatalogDataSourceActions {
constructor() {
- this.generateActions('loadCatalogsSuccess', 'loadCatalogsError', 'deleteCatalogItemSuccess', 'deleteCatalogItemError', 'saveCatalogItemSuccess', 'saveCatalogItemError');
- }
-
+ this.generateActions(
+ 'loadCatalogsSuccess',
+ 'loadCatalogsError',
+ 'deleteCatalogItemSuccess',
+ 'deleteCatalogItemError',
+ 'saveCatalogItemSuccess',
+ 'saveCatalogItemError');
+ }
}
export default alt.createActions(CatalogDataSourceActions);
-
/*
*
* Copyright 2016 RIFT.IO Inc
class ComposerAppActions {
constructor() {
- this.generateActions('showError', 'clearError', 'setDragState', 'propertySelected', 'showJsonViewer', 'closeJsonViewer', 'selectModel', 'outlineModel', 'clearSelection', 'enterFullScreenMode', 'exitFullScreenMode',
- 'showAssets', 'showDescriptor');
+ this.generateActions(
+ 'showError',
+ 'clearError',
+ 'setDragState',
+ 'propertySelected',
+ 'showJsonViewer',
+ 'closeJsonViewer',
+ 'selectModel', 'outlineModel',
+ 'clearSelection',
+ 'enterFullScreenMode',
+ 'exitFullScreenMode',
+ 'showAssets',
+ 'showDescriptor',
+ 'recordDescriptorError');
}
}
-export default alt.createActions(ComposerAppActions);
+export default alt.createActions(ComposerAppActions);
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import alt from '../alt';
+
+class DescriptorEditorActions {
+
+ constructor() {
+ this.generateActions(
+ 'setError',
+ 'setValue',
+ 'assignValue',
+ 'addListItem',
+ 'removeListItem',
+ 'showHelpForNothing',
+ 'showHelpForAll',
+ 'showHelpOnFocus',
+ 'setOpenState',
+ 'collapseAllPanels',
+ 'expandAllPanels',
+ 'expandPanel',
+ 'showPanelsWithData'
+ );
+ }
+
+}
+
+export default alt.createActions(DescriptorEditorActions);
\ No newline at end of file
if (e.detail && e.detail.side) {
// a ResizeManager event
- this.dispatch(PanelResizeAction.buildResizeManagerInfo(e))
+ return PanelResizeAction.buildResizeManagerInfo(e);
} else {
// a window event
- this.dispatch(PanelResizeAction.buildWindowResizeInfo(e));
+ return PanelResizeAction.buildWindowResizeInfo(e);
}
}
*/
'use strict';
-var Alt = require('alt');
-var alt = new Alt();
-
-export default alt;
+import Alt from 'widgets/skyquake_container/skyquakeAltInstance.js';
+export default Alt;
label: null,
title: null,
src: null,
+ disabled: false,
onClick: () => {}
};
},
const title = this.props.title;
const draggable = this.props.draggable;
const className = ClassNames(this.props.className, 'Button');
+ let style = {
+ }
+ if(this.props.disabled) {
+ style.pointerEvents = 'none';
+ style.cursor = 'not-allowed';
+ style.opacity = 0.25;
+ }
return (
- <div className={className} onClick={this.props.onClick} title={title} draggable={draggable} onDragStart={this.props.onDragStart}>
+ <div className={className} onClick={this.props.onClick} title={title} draggable={draggable} onDragStart={this.props.onDragStart} style={style}>
{ src ? <img src={src} /> : null }
{label}
</div>
import EditForwardingGraphPaths from './EditorForwardingGraph/EditForwardingGraphPaths'
import SelectionManager from '../libraries/SelectionManager'
import DescriptorModelIconFactory from '../libraries/model/IconFactory'
-
import FileManager from './filemanager/FileManager.jsx';
+import { isRBACValid } from 'widgets/skyquake_rbac/skyquakeRBAC';
+import ROLES from 'utils/roleConstants.js';
+import ConfigPrimitiveParameters from './ConfigPrimitiveParameters/ConfigPrimitiveParameters'
import '../styles/CanvasPanel.scss'
const CanvasPanel = React.createClass({
left: this.props.layout.left
};
var req = require.context("../", true, /^\.\/.*\.svg$/);
+ const User = this.props.User || {};
+ const isModifiableByUser = isRBACValid(User, [ROLES.PROJECT.PROJECT_ADMIN, ROLES.PROJECT.CATALOG_ADMIN]);
const hasItem = this.props.containers.length !== 0;
const isEditingNSD = DescriptorModelFactory.isNetworkService(this.props.containers[0]);
const isDescriptorView = (this.props.panelTabShown == 'descriptor');
const hasNoCatalogs = this.props.hasNoCatalogs;
- const bodyComponent = hasItem ? <CatalogItemCanvasEditor zoom={this.props.zoom} isShowingMoreInfo={this.props.showMore} containers={this.props.containers}/> : messages.canvasWelcome();
+ const bodyComponent = hasItem ? (
+ <CatalogItemCanvasEditor
+ readOnly={!isModifiableByUser}
+ zoom={this.props.zoom}
+ isShowingMoreInfo={this.props.showMore}
+ containers={this.props.containers} />) : messages.canvasWelcome();
const viewFiles = this.props.panelTabShown == 'assets';
- const viewButtonTabs = !hasItem ? null : (
- <div className="CanvasPanelTabs">
- <div className="CatalogFilter">
- <button className={isDescriptorView ? '-selected' : ''} onClick={ComposerAppActions.showDescriptor}>
- Descriptor
+ const viewButtonTabs = !hasItem ? null : (
+ <div className="CanvasPanelTabs">
+ <div className="CatalogFilter">
+ <button className={isDescriptorView ? '-selected' : ''} onClick={ComposerAppActions.showDescriptor}>
+ Descriptor
</button>
- {
- this.props.files ?
- <button className={!isDescriptorView ? '-selected' : ''} onClick={ComposerAppActions.showAssets}>
- Assets
+ {
+ this.props.files ?
+ <button className={!isDescriptorView ? '-selected' : ''} onClick={ComposerAppActions.showAssets}>
+ Assets
</button>
: null
- }
- </div>
+ }
</div>
- )
+ </div>
+ )
+ //CanvasPanelTray panel to display
+ let displayedPanel = null;
+ switch (this.props.displayedPanel) {
+ case 'forwarding': displayedPanel = (<EditForwardingGraphPaths containers={this.props.containers} />); break;
+ case 'parameter': displayedPanel = (<ConfigPrimitiveParameters containers={this.props.containers} />); break;
+ default: displayedPanel = (<div><p className="welcome-message">Please select a tab</p></div>); break;
+ }
return (
- <div id="canvasPanelDiv" className="CanvasPanel" style={style} onDragOver={this.onDragOver} onDrop={this.onDrop}>
- <div onDoubleClick={this.onDblClickOpenFullScreen} className="CanvasPanelHeader panel-header" data-resizable="limit_bottom">
+ <div id="canvasPanelDiv" className="CanvasPanel" style={style}
+ onDragOver={isModifiableByUser ? this.onDragOver : null} onDrop={isModifiableByUser ? this.onDrop : null}>
+ <div onDoubleClick={this.onDblClickOpenFullScreen} className="CanvasPanelHeader panel-header" data-resizable="limit_bottom">
<h1>
{hasItem ? <img src={req('./' + DescriptorModelIconFactory.getUrlForType(this.props.containers[0].type, 'black'))} width="20px" /> : null}
<span className="model-name">{this.props.title}</span>
</h1>
</div>
{viewButtonTabs}
- <div className="CanvasPanelBody panel-body" style={{marginRight: this.props.layout.right, bottom: this.props.layout.bottom}} >
- {hasNoCatalogs ? null : viewFiles ? <FileManager files={this.props.files} type={this.props.type} item={this.props.item} filesState={this.props.filesState} newPathName={this.props.newPathName} /> : bodyComponent}
+ <div className="CanvasPanelBody panel-body" style={{ marginRight: this.props.layout.right, bottom: this.props.layout.bottom }} >
+ {hasNoCatalogs ? null : viewFiles ? <FileManager files={this.props.files} type={this.props.type} item={this.props.item} filesState={this.props.filesState} newPathName={this.props.newPathName} User={User} /> : bodyComponent}
</div>
{
isDescriptorView ?
- <CanvasZoom zoom={this.props.zoom} style={{bottom: this.props.layout.bottom + 20}}/>
+ <CanvasZoom zoom={this.props.zoom} style={{ bottom: this.props.layout.bottom + 20 }} />
: null
}
- <CanvasPanelTray layout={this.props.layout} show={isEditingNSD && isDescriptorView}>
- <EditForwardingGraphPaths containers={this.props.containers} />
+ <CanvasPanelTray layout={this.props.layout} displayedPanel={this.props.displayedPanel} show={isEditingNSD && isDescriptorView}>
+ {displayedPanel}
</CanvasPanelTray>
</div>
);
handleDropCanvasAction(event, data) {
const action = cc.camel('on-' + data.action);
if (typeof this[action] === 'function') {
- if (this[action]({clientX: event.clientX, clientY: event.clientY})) {
+ if (this[action]({ clientX: event.clientX, clientY: event.clientY })) {
event.preventDefault();
}
} else {
} else if (DescriptorModelFactory.isNetworkService(currentItem)) {
// so add the item to the nsd and re-render the canvas
switch (data.item.uiState.type) {
- case 'vnfd':
- this.onAddVnfd(data.item, {clientX: event.clientX, clientY: event.clientY});
- break;
- case 'pnfd':
- this.onAddPnfd(data.item, {clientX: event.clientX, clientY: event.clientY});
- break;
- default:
- console.warn(`Unknown catalog-item type. Expect type "nsd", "vnfd" or "pnfd" but got ${data.item.uiState.type}.`);
+ case 'vnfd':
+ this.onAddVnfd(data.item, { clientX: event.clientX, clientY: event.clientY });
+ break;
+ case 'pnfd':
+ this.onAddPnfd(data.item, { clientX: event.clientX, clientY: event.clientY });
+ break;
+ default:
+ console.warn(`Unknown catalog-item type. Expect type "nsd", "vnfd" or "pnfd" but got ${data.item.uiState.type}.`);
}
} else {
// otherwise the default action is to open the item
right: props.layout.right,
display: props.show ? false : 'none'
};
+ const PANEL = {
+ FORWARD: 'forwarding',
+ PARAMETER: 'parameter'
+ }
const classNames = ClassNames('CanvasPanelTray', {'-with-transitions': !document.body.classList.contains('resizing')});
function onClickToggleOpenClose(event) {
if (event.defaultPrevented) return;
event.preventDefault();
// don't toggle if the user was resizing
if (!uiTransient.isResizing) {
- CanvasPanelTrayActions.toggleOpenClose();
+ CanvasPanelTrayActions.toggleOpenClose(event);
}
event.target.removeEventListener('mousemove', onMouseMove, true);
}
const isOpen = style.height > 25;
return (
<div className={classNames} data-resizable="top" data-resizable-handle-offset="4" style={style}>
- <h1 data-open-close-icon={isOpen ? 'open' : 'closed'} onMouseDownCapture={onMouseDown} onClick={onClickToggleOpenClose}>Forwarding Graphs</h1>
+ <div className="CanvasPanelTray-buttons" onClick={onClickToggleOpenClose}>
+ <button
+ style={{flex: '1 1 auto'}}
+ className={ClassNames({'-selected': props.displayedPanel === PANEL.FORWARD})}
+ onMouseDownCapture={onMouseDown}
+ data-event={PANEL.FORWARD}>
+ Forwarding Graphs
+ </button>
+
+ <button
+ style={{flex: '1 1 auto', borderLeft: '1px solid white'}}
+ className={ClassNames({'-selected': props.displayedPanel === PANEL.PARAMETER})}
+ onMouseDownCapture={onMouseDown}
+ data-event={PANEL.PARAMETER}>
+ Config Parameter Map
+ </button>
+ <div data-open-close-icon={isOpen ? 'open' : 'closed'} style={{flex: '0 1', width: '40px', height: '25px', cursor: 'pointer'}} data-event='arrow'>
+
+ </div>
+ </div>
<div className="tray-body">
{props.children}
</div>
</div>
);
-}
\ No newline at end of file
+}
import '../styles/CatalogItemCanvasEditor.scss'
import '../styles/DescriptorGraph.scss'
-const CatalogItemCanvasEditor = React.createClass({
- mixins: [PureRenderMixin],
- getInitialState() {
- return {
- graph: null
- };
- },
- getDefaultProps() {
- return {
+class CatalogItemCanvasEditor extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ graph: null,
zoom: 100,
containers: [],
isShowingMoreInfo: false
};
- },
- componentWillMount() {
- },
+ }
+
componentDidMount() {
const element = ReactDOM.findDOMNode(this.refs.descriptorGraph);
const options = {
- zoom: this.props.zoom
+ zoom: this.props.zoom,
+ readOnly: this.props.readOnly
};
const graph = new DescriptorGraph(element, options);
graph.containers = this.props.containers;
this.setState({graph: graph});
- },
+ }
+
componentDidUpdate() {
this.state.graph.containers = this.props.containers;
const isNSD = this.props.containers[0] && this.props.containers[0].uiState.type === 'nsd';
this.state.graph.showMoreInfo = true;
}
this.state.graph.update();
- },
+ }
+
componentWillUnmount() {
this.state.graph.destroy();
- },
+ }
+
render() {
const graph = this.state.graph;
if (graph) {
</div>
);
}
-});
+};
export default CatalogItemCanvasEditor;
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
import React from 'react'
import PureRenderMixin from 'react-addons-pure-render-mixin'
import EditDescriptorModelProperties from './EditDescriptorModelProperties'
+import '../styles/CatalogItemDetailsEditor.scss'
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
const CatalogItemDetailsEditor = React.createClass({
mixins: [PureRenderMixin],
width: 0
};
},
+ contextTypes: {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+ },
componentWillMount() {
},
componentDidMount() {
componentWillUnmount() {
},
render() {
+ const User = this.context.userProfile;
- const container = this.props.container || {model: {}, uiState: {}};
+ const container = this.props.container || { model: {}, uiState: {} };
if (!(container && container.model && container.uiState)) {
return null;
}
-
+ const height = this.props.height;
+ const style = height > 0 ? { height: height + 'px' } : {};
return (
- <div className="CatalogItemDetailsEditor">
+ <div className="CatalogItemDetailsEditor" style={style}>
<form name="details-descriptor-editor-form">
<div className="properties-group">
- <EditDescriptorModelProperties container={this.props.container} width={this.props.width} />
+ {
+ isRBACValid(User, [PROJECT_ROLES.PROJECT_ADMIN, PROJECT_ROLES.CATALOG_ADMIN]) ?
+ <EditDescriptorModelProperties
+ container={this.props.container}
+ idMaker={this.props.idMaker}
+ showHelp={this.props.showHelp}
+ collapsePanelsByDefault={this.props.collapsePanelsByDefault}
+ openPanelsWithData={this.props.openPanelsWithData}
+ width={this.props.width} />
+ :<EditDescriptorModelProperties
+ container={this.props.container}
+ idMaker={this.props.idMaker}
+ showHelp={this.props.showHelp}
+ collapsePanelsByDefault={this.props.collapsePanelsByDefault}
+ openPanelsWithData={this.props.openPanelsWithData}
+ width={this.props.width}
+ readOnly={true} />
+ }
</div>
</form>
</div>
}
});
-
export default CatalogItemDetailsEditor;
import '../styles/CatalogItems.scss'
import imgFile from 'file!../images/vendor-riftio.png'
+const DEFAULT_NSD_ICON = require('style/img/catalog-nsd-default.svg');
+const DEFAULT_VNFD_ICON = require('style/img/catalog-vnfd-default.svg');
+const DEFAULT_ICON = require('style/img/catalog-default.svg');
+
+function renderVersion (version) {
+ if (version) {
+ return (<span className='version'>{version}</span>);
+ } // else return null by default
+};
+function getImageErrorHandler (type) {
+ return type === 'nsd' ? handleNsdImageError : type === 'vnfd' ? handleVnfdImageError : handleImageError;
+}
+function handleImageError (e, image) {
+ console.log('Bad logo path, using default');
+ e.target.src = image || DEFAULT_ICON;
+};
+function handleNsdImageError (e) {
+ handleImageError(e, DEFAULT_NSD_ICON);
+};
+function handleVnfdImageError (e) {
+ handleImageError(e, DEFAULT_VNFD_ICON);
+};
+
const CatalogItems = React.createClass({
mixins: [PureRenderMixin],
getInitialState() {
onChange(state) {
this.setState(state);
},
- renderVersion(version) {
- if (version) {
- return (<span className='version'>{version}</span>);
- } // else return null by default
- },
- handleImageError(e) {
- console.log('Bad logo path, using default');
- e.target.src = require('style/img/catalog-default.svg');
- },
render() {
- const self = this;
const onDragStart = function(event) {
const data = {type: 'catalog-item', item: this};
event.dataTransfer.effectAllowed = 'copy';
// single clicking an item is handled by ComposerApp::onClick handler
//CatalogItemsActions.selectCatalogItem(this);
};
- const cleanDataURI = this.cleanDataURI;
+ const cleanDataURI = function (imageString, type, id) {
+ if (/\bbase64\b/g.test(imageString)) {
+ return imageString;
+ } else if (/<\?xml\b/g.test(imageString)) {
+ const imgStr = imageString.substring(imageString.indexOf('<?xml'));
+ return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(imgStr);
+ } else if (/\.(svg|png|gif|jpeg|jpg)$/.test(imageString)) {
+ return 'assets/logos/' + type + '/' + id + '/' + imageString;
+ // return require('../images/logos/' + imageString);
+ }
+ return type === 'nsd' ? DEFAULT_NSD_ICON : type === 'vnfd' ? DEFAULT_VNFD_ICON : DEFAULT_ICON;
+ }
const items = this.getCatalogItems().map(function (d) {
const isNSD = d.uiState.type === 'nsd';
const isVNFD = d.uiState.type === 'vnfd';
const isOpenForEdit = d.uiState.isOpenForEdit;
const spanClassNames = ClassNames({'-is-selected': isSelected, '-is-open-for-edit': isOpenForEdit});
const sectionClassNames = ClassNames('catalog-item', {'-is-modified': isModified, '-is-deleted': isDeleted});
- const instanceCount = d.uiState['instance-ref-count'];
- const instanceCountLabel = isNSD && instanceCount ? <span>({instanceCount})</span> : null;
let type;
if(isNSD) {
type = 'nsd';
{isModified ? <div className="-is-modified-indicator" title="This descriptor has changes."></div> : null}
<div className="type-header">{type}</div>
<dl>
- <dt className="name">{d.name} {instanceCountLabel}</dt>
+ <dt className="name">{d.name}</dt>
<dd className="logo">
- <img className="logo" src={cleanDataURI(d['logo'], type, d.id)} draggable="false" onError={self.handleImageError} />
+ <img className="logo" src={cleanDataURI(d['logo'], type, d.id)} draggable="false" onError={getImageErrorHandler(type)} />
</dd>
<dd className="short-name" title={d.name}>{d['short-name']}</dd>
<dd className="description">{d.description}</dd>
- <dd className="vendor">{d.vendor || d.provider} {self.renderVersion(d.version)}</dd>
+ <dd className="vendor">{d.vendor || d.provider} {renderVersion(d.version)}</dd>
</dl>
</div>
</div>
</div>
);
},
- cleanDataURI(imageString, type, id) {
- if (/\bbase64\b/g.test(imageString)) {
- return imageString;
- } else if (/<\?xml\b/g.test(imageString)) {
- const imgStr = imageString.substring(imageString.indexOf('<?xml'));
- return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(imgStr);
- } else if (/\.(svg|png|gif|jpeg|jpg)$/.test(imageString)) {
- return 'assets/logos/' + type + '/' + id + '/' + imageString;
- // return require('../images/logos/' + imageString);
- }
- if(type == 'nsd' || type == 'vnfd') {
- return require('style/img/catalog-'+type+'-default.svg');
- }
- return require('style/img/catalog-default.svg');
- },
getCatalogItems() {
const catalogFilter = (d) => {return d.type === this.props.filterByType};
return this.state.catalogs.filter(catalogFilter).reduce((result, catalog) => {
import CatalogPanelStore from '../stores/CatalogPanelStore'
import LoadingIndicator from './LoadingIndicator'
import SelectionManager from '../libraries/SelectionManager'
+import { isRBACValid } from 'widgets/skyquake_rbac/skyquakeRBAC';
+import ROLES from 'utils/roleConstants.js';
import '../styles/CatalogPanel.scss'
document.body.removeEventListener('dragend', this.onDragEnd);
window.removeEventListener('dragend', this.onDragEnd);
},
+ contextTypes: {
+ userProfile: React.PropTypes.object
+ },
render() {
+ const User = this.context.userProfile || {};
+ const isModifiableByUser = isRBACValid(User, [ROLES.PROJECT.PROJECT_ADMIN, ROLES.PROJECT.CATALOG_ADMIN]);
const onDropCatalogItem = e => {
e.preventDefault();
const isDraggingFiles = uiTransientState.isDragging && uiTransientState.isDraggingFiles;
const updateDropZone = createDropZone.bind(this, UploadDropZone.ACTIONS.update, '.action-update-catalog-package');
const onboardDropZone = createDropZone.bind(this, UploadDropZone.ACTIONS.onboard, '.action-onboard-catalog-package');
- const className = ClassNames('CatalogPanel', {'-is-tray-open': this.state.isTrayOpen});
+ const className = ClassNames('CatalogPanel', { '-is-tray-open': this.state.isTrayOpen });
const hasNoCatalogs = this.props.hasNoCatalogs;
const isLoading = this.props.isLoading;
+ const packageManagerPanel = (
+ <CatalogPanelTray show={this.state.isTrayOpen}>
+ <DropZonePanel show={isDraggingItem} title="Drop catalog item to export.">
+ <DropTarget className="action-export-catalog-items" onDrop={onDropCatalogItem}>
+ <span>Export catalog item.</span>
+ </DropTarget>
+ </DropZonePanel>
+ <DropZonePanel show={isDraggingFiles}>
+ <DropTarget className="action-onboard-catalog-package" dropZone={onboardDropZone} onDrop={onDropOnboardPackage}>
+ <span>On-board new catalog package.</span>
+ </DropTarget>
+ <DropTarget className="action-update-catalog-package" dropZone={updateDropZone} onDrop={onDropUpdatePackage}>
+ <span>Update existing catalog package.</span>
+ </DropTarget>
+ </DropZonePanel>
+ <CatalogPackageManager />
+ </CatalogPanelTray>
+ );
return (
- <div className={className} data-resizable="right" data-resizable-handle-offset="0 6" style={{width: this.props.layout.left}}>
- <CatalogPanelToolbar />
+ <div className={className} data-resizable="right" data-resizable-handle-offset="0 6" style={{ width: this.props.layout.left }}>
+ <CatalogPanelToolbar rbacDisabled={this.props.rbacDisabled} />
<div className="CatalogPanelBody">
{(() => {
if (isLoading) {
)
}
if (hasNoCatalogs) {
- return messages.catalogWelcome;
+ return messages.catalogWelcome(isModifiableByUser);
}
return (
<div>
);
})()}
</div>
- <CatalogPanelTray show={this.state.isTrayOpen}>
- <DropZonePanel show={isDraggingItem} title="Drop catalog item to export.">
- <DropTarget className="action-export-catalog-items" onDrop={onDropCatalogItem}>
- <span>Export catalog item.</span>
- </DropTarget>
- </DropZonePanel>
- <DropZonePanel show={isDraggingFiles}>
- <DropTarget className="action-onboard-catalog-package" dropZone={onboardDropZone} onDrop={onDropOnboardPackage}>
- <span>On-board new catalog package.</span>
- </DropTarget>
- <DropTarget className="action-update-catalog-package" dropZone={updateDropZone} onDrop={onDropUpdatePackage}>
- <span>Update existing catalog package.</span>
- </DropTarget>
- </DropZonePanel>
- <CatalogPackageManager />
- </CatalogPanelTray>
+ {isModifiableByUser ? packageManagerPanel : null}
</div>
);
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
import imgUpdate from '../../../node_modules/open-iconic/svg/rain.svg'
import imgDownload from '../../../node_modules/open-iconic/svg/cloud-download.svg'
import imgDelete from '../../../node_modules/open-iconic/svg/trash.svg'
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
const CatalogHeader = React.createClass({
mixins: [PureRenderMixin],
},
componentWillUnmount() {
},
+ contextTypes: {
+ userProfile: React.PropTypes.object
+ },
render() {
+ const disabled = !isRBACValid(this.context.userProfile, [PROJECT_ROLES.PROJECT_ADMIN, PROJECT_ROLES.CATALOG_ADMIN]);
return (
<div className="CatalogPanelToolbar">
<h1>Descriptor Catalogs</h1>
<div className="btn-bar">
<div className="btn-group">
- <Button type="image" title="OnBoard a catalog package" className="action-onboard-catalog-package" onClick={this.onClickOnBoardCatalog} src={imgOnboard} />
- <Button type="image" title="Update a catalog package" className="action-update-catalog-package" onClick={this.onClickUpdateCatalog} src={imgUpdate} />
- <Button type="image" title="Export selected catalog item(s)" className="action-export-catalog-items" onClick={this.onClickExportCatalogItems} src={imgDownload} />
+ <Button type="image" title="OnBoard a catalog package" className="action-onboard-catalog-package" onClick={this.onClickOnBoardCatalog} src={imgOnboard} disabled={disabled}/>
+ <Button type="image" title="Update a catalog package" className="action-update-catalog-package" onClick={this.onClickUpdateCatalog} src={imgUpdate} disabled={disabled}/>
+ <Button type="image" title="Export selected catalog item(s)" className="action-export-catalog-items" onClick={this.onClickExportCatalogItems} src={imgDownload} disabled={disabled}/>
</div>
<div className="btn-group">
<div className="menu">
- <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'nsd')} src={imgAdd} />
+ <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'nsd')} src={imgAdd} disabled={disabled}/>
<div className="sub-menu">
- <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'nsd')} src={imgAdd} label="Add NSD" />
- <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'vnfd')} src={imgAdd} label="Add VNFD" />
+ <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'nsd')} src={imgAdd} label="Add NSD" disabled={disabled}/>
+ <Button type="image" title="Create a new catalog item" className="action-create-catalog-item" onClick={this.onClickCreateCatalogItem.bind(null, 'vnfd')} src={imgAdd} label="Add VNFD" disabled={disabled}/>
</div>
</div>
- <Button type="image" title="Copy catalog item" className="action-copy-catalog-item" onClick={this.onClickDuplicateCatalogItem} src={imgCopy} />
+ <Button type="image" title="Copy catalog item" className="action-copy-catalog-item" onClick={this.onClickDuplicateCatalogItem} src={imgCopy} disabled={disabled}/>
</div>
<div className="btn-group">
- <Button type="image" title="Delete catalog item" className="action-delete-catalog-item" onClick = {this.onClickDeleteCatalogItem} src={imgDelete} />
+ <Button type="image" title="Delete catalog item" className="action-delete-catalog-item" onClick = {this.onClickDeleteCatalogItem} src={imgDelete} disabled={disabled}/>
</div>
</div>
</div>
import CatalogItemsActions from '../actions/CatalogItemsActions'
import CommonUtils from 'utils/utils.js'
import FileManagerActions from './filemanager/FileManagerActions';
+import { SkyquakeRBAC, isRBACValid } from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+import _isEmpty from 'lodash/isEmpty';
+import _isEqual from 'lodash/isEqual';
+
import 'normalize.css'
import '../styles/AppRoot.scss'
import 'style/layout.scss'
const preventDefault = e => e.preventDefault();
const clearDragState = () => ComposerAppActions.setDragState(null);
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
+const CATALOG_POLLING_INTERVAL = 2000;
+
const ComposerApp = React.createClass({
- mixins: [PureRenderMixin],
- getInitialState() {
- return ComposerAppStore.getState();
- },
- getDefaultProps() {
- return {};
- },
- componentWillMount() {
- if (clearLocalStorage) {
- window.localStorage.clear();
- }
- if(this.item) {
+ getInitialState() {
+ return ComposerAppStore.getState();
+ },
+ getDefaultProps() {
+ return {};
+ },
+ contextTypes: {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+ },
+ componentWillUpdate(nextProps, nextState, nextContext) {
+ if (!_isEmpty(nextContext.userProfile)) {
+ CatalogDataStore.setUserProfile(nextContext.userProfile);
+ }
+ },
+ shouldComponentUpdate: function (nextProps, nextState, nextContext) {
+ if (!this.userProfile && !_isEmpty(nextContext.userProfile)) {
+ this.userProfile = nextContext.userProfile;
+ CatalogDataStore.setUserProfile(nextContext.userProfile);
+ return true;
+ }
+ return !_isEqual(this.props, nextProps) ||
+ !_isEqual(this.state, nextState);
+ },
+ componentWillMount() {
+ if (clearLocalStorage) {
+ window.localStorage.clear();
+ }
+ if (this.item) {
FileManagerActions.openFileManagerSockets();
}
- this.state.isLoading = CatalogDataStore.getState().isLoading;
- ComposerAppStore.listen(this.onChange);
- CatalogDataStore.listen(this.onCatalogDataChanged);
- window.addEventListener('resize', this.resize);
- window.onbeforeunload = this.onBeforeUnload;
- // prevent browser from downloading any drop outside of our specific drop zones
- window.addEventListener('dragover', preventDefault);
- window.addEventListener('drop', preventDefault);
- // ensure drags initiated in the app clear the state on drop
- window.addEventListener('drop', clearDragState);
- DeletionManager.addEventListeners();
- },
- componentWillUnmount() {
- window.removeEventListener('resize', this.resize);
- window.removeEventListener('dragover', preventDefault);
- window.removeEventListener('drop', preventDefault);
- window.removeEventListener('drop', clearDragState);
+ this.state.isLoading = CatalogDataStore.getState().isLoading;
+ ComposerAppStore.listen(this.onChange);
+ CatalogDataStore.listen(this.onCatalogDataChanged);
+ window.addEventListener('resize', this.resize);
+ // prevent browser from downloading any drop outside of our specific drop zones
+ window.addEventListener('dragover', preventDefault);
+ window.addEventListener('drop', preventDefault);
+ // ensure drags initiated in the app clear the state on drop
+ window.addEventListener('drop', clearDragState);
+ DeletionManager.addEventListeners();
+ },
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.resize);
+ window.removeEventListener('dragover', preventDefault);
+ window.removeEventListener('drop', preventDefault);
+ window.removeEventListener('drop', clearDragState);
FileManagerActions.closeFileManagerSockets();
- // resizeManager automatically registered its event handlers
- resizeManager.removeAllEventListeners();
- ComposerAppStore.unlisten(this.onChange);
- CatalogDataStore.unlisten(this.onCatalogDataChanged);
- DeletionManager.removeEventListeners();
- TooltipManager.removeEventListeners();
- },
- componentDidMount() {
- resizeManager.addAllEventListeners();
- const snapshot = window.localStorage.getItem('composer');
- if (snapshot) {
- alt.bootstrap(snapshot);
- }
- document.body.addEventListener('keydown', (event) => {
- // prevent details editor form from blowing up the app
- const ENTER_KEY = 13;
- if (event.which === ENTER_KEY) {
- event.preventDefault();
- return false;
- }
- });
- const appRootElement = ReactDOM.findDOMNode(this.refs.appRoot);
- TooltipManager.addEventListeners(appRootElement);
- SelectionManager.onClearSelection = () => {
- if (this.state.item) {
- CatalogItemsActions.catalogItemMetaDataChanged.defer(this.state.item);
- }
- };
- },
- componentDidUpdate() {
- if (this.state.fullScreenMode) {
- document.body.classList.add('-is-full-screen');
- } else {
- document.body.classList.remove('-is-full-screen');
- }
- SelectionManager.refreshOutline();
- },
- resize(e) {
- PanelResizeAction.resize(e);
- },
- getModel() {
- let html;
- let self = this;
- DescriptorModelMetaFactory.init().then(function(){
-
- self.setState({
- hasModel: true
- })
- });
- },
- render() {
- let html = null;
- let self = this;
- if(this.state.hasModel) {
+ // resizeManager automatically registered its event handlers
+ resizeManager.removeAllEventListeners();
+ ComposerAppStore.unlisten(this.onChange);
+ CatalogDataStore.unlisten(this.onCatalogDataChanged);
+ DeletionManager.removeEventListeners();
+ TooltipManager.removeEventListeners();
+ if (this.catalogMonitorId) {
+ clearTimeout(this.catalogMonitorId);
+ }
+ },
+ componentDidMount() {
+ resizeManager.addAllEventListeners();
+ const snapshot = window.localStorage.getItem('composer');
+ if (snapshot) {
+ alt.bootstrap(snapshot);
+ }
+ document.body.addEventListener('keydown', (event) => {
+ // prevent details editor form from blowing up the app
+ const ENTER_KEY = 13;
+ if (event.which === ENTER_KEY) {
+ event.preventDefault();
+ return false;
+ }
+ });
+ const loadCatalogs = () => {
+ CatalogDataStore.loadCatalogs();
+ if (CATALOG_POLLING_INTERVAL) {
+ this.catalogMonitorId = setTimeout(loadCatalogs, CATALOG_POLLING_INTERVAL);
+ }
+ };
+ loadCatalogs();
+ DescriptorModelMetaFactory.init().then(() => this.setState({ hasModel: true }));
+ },
+ componentDidUpdate() {
+ if (this.state.fullScreenMode) {
+ document.body.classList.add('-is-full-screen');
+ } else {
+ document.body.classList.remove('-is-full-screen');
+ }
+ SelectionManager.refreshOutline();
+ },
+ resize(e) {
+ PanelResizeAction.resize(e);
+ },
+ render() {
+ let html = null;
+ let self = this;
+ const User = this.userProfile || {};
+ const rbacDisabled = !isRBACValid(User, [PROJECT_ROLES.PROJECT_ADMIN, PROJECT_ROLES.CATALOG_ADMIN]);
+ if (this.state.hasModel) {
- function onClickUpdateSelection(event) {
- if (event.defaultPrevented) {
- return
- }
- const element = SelectionManager.getClosestElementWithUID(event.target);
- if (element) {
- SelectionManager.select(element);
- SelectionManager.refreshOutline();
- event.preventDefault();
- } else {
- SelectionManager.clearSelectionAndRemoveOutline();
- }
- }
+ function onClickUpdateSelection(event) {
+ if (event.defaultPrevented) {
+ return
+ }
+ const element = SelectionManager.getClosestElementWithUID(event.target);
+ if (element) {
+ SelectionManager.select(element);
+ SelectionManager.refreshOutline();
+ event.preventDefault();
+ } else {
+ if (event.target.offsetParent && !event.target.offsetParent.classList.contains("tray-body")) {
+ SelectionManager.clearSelectionAndRemoveOutline();
+ }
+ }
+ }
+ let cpNumber = 0;
+ let AppHeader = (<div className="AppHeader">
+ <RiftHeader />
+ </div>);
+ // AppHeader = null;
+ const classNames = ClassNames('ComposerApp');
+ const isNew = self.state.item && self.state.item.uiState.isNew;
+ const hasItem = self.state.item && self.state.item.uiState;
+ const isModified = self.state.item && self.state.item.uiState.modified;
+ const isEditingNSD = self.state.item && self.state.item.uiState && /nsd/.test(self.state.item.uiState.type);
+ const isEditingVNFD = self.state.item && self.state.item.uiState && /vnfd/.test(self.state.item.uiState.type);
+ const containers = [self.state.item].reduce(DescriptorModelFactory.buildCatalogItemFactory(CatalogDataStore.getState().catalogs), []);
- let AppHeader = (<div className="AppHeader">
- <RiftHeader />
- </div>);
- // AppHeader = null;
- const classNames = ClassNames('ComposerApp');
- const isNew = self.state.item && self.state.item.uiState.isNew;
- const hasItem = self.state.item && self.state.item.uiState;
- const isModified = self.state.item && self.state.item.uiState.modified;
- const isEditingNSD = self.state.item && self.state.item.uiState && /nsd/.test(self.state.item.uiState.type);
- const isEditingVNFD = self.state.item && self.state.item.uiState && /vnfd/.test(self.state.item.uiState.type);
- const containers = self.state.containers;
- const canvasTitle = containers.length ? containers[0].model.name : '';
- const hasNoCatalogs = CatalogDataStore.getState().catalogs.length === 0;
- const isLoading = self.state.isLoading;
+ containers.filter(d => DescriptorModelFactory.isConnectionPoint(d)).forEach(d => {
+ d.cpNumber = ++cpNumber;
+ containers.filter(d => DescriptorModelFactory.isVnfdConnectionPointRef(d)).filter(ref => ref.key === d.key).forEach(ref => ref.cpNumber = d.cpNumber);
+ });
+ const canvasTitle = containers.length ? containers[0].model.name : '';
+ const hasNoCatalogs = CatalogDataStore.getState().catalogs.length === 0;
+ const isLoading = self.state.isLoading;
//Bridge element for Crouton fix. Should eventually put Composer on same flux context
const Bridge = this.state.ComponentBridgeElement;
- html = (
- <div ref="appRoot" id="RIFT_wareLaunchpadComposerAppRoot" className="AppRoot" onClick={onClickUpdateSelection}>
+ html = (
+ <div ref={element => TooltipManager.addEventListeners(element)} id="RIFT_wareLaunchpadComposerAppRoot" className="AppRoot" onClick={onClickUpdateSelection}>
<Bridge />
- <i className="corner-accent top left" />
- <i className="corner-accent top right" />
- <i className="corner-accent bottom left" />
- <i className="corner-accent bottom right" />
- {AppHeader}
- <div className="AppBody">
- <div className={classNames}>
- <CatalogPanel layout={self.state.layout}
- isLoading={isLoading}
- hasNoCatalogs={hasNoCatalogs}
- filterByType={self.state.filterCatalogByTypeValue} />
- <CanvasPanel layout={self.state.layout}
- hasNoCatalogs={hasNoCatalogs}
- showMore={self.state.showMore}
- containers={containers}
- title={canvasTitle}
- zoom={self.state.zoom}
- panelTabShown={self.state.panelTabShown}
- files={self.state.files}
- filesState={self.state.filesState}
- newPathName={self.state.newPathName}
- item={self.state.item}
- type={self.state.filterCatalogByTypeValue}
- />
- {
- (self.state.panelTabShown == 'descriptor') ?
- <DetailsPanel layout={self.state.layout}
- hasNoCatalogs={hasNoCatalogs}
- showMore={self.state.showMore}
- containers={containers}
- showJSONViewer={self.state.showJSONViewer} />
- : null
- }
-
- <ComposerAppToolbar layout={self.state.layout}
- showMore={self.state.showMore}
- isEditingNSD={isEditingNSD}
- isEditingVNFD={isEditingVNFD}
- isModified={isModified}
- isNew={isNew}
- disabled={!hasItem}
- onClick={event => event.stopPropagation()}
- panelTabShown={self.state.panelTabShown}/>
- </div>
- </div>
- <ModalOverlay />
- </div>
- );
- } else {
- this.getModel();
- }
- return html;
- },
- onChange(state) {
- this.setState(state);
- },
- onCatalogDataChanged(catalogDataState) {
- const catalogs = catalogDataState.catalogs;
- const unsavedChanges = catalogs.reduce((result, catalog) => {
- if (result) {
- return result;
- }
- return catalog.descriptors.reduce((result, descriptor) => {
- if (result) {
- return result;
- }
- return descriptor.uiState.modified;
- }, false);
- }, false);
- this.setState({
- unsavedChanges: unsavedChanges,
- isLoading: catalogDataState.isLoading
- });
- },
- onBeforeUnload() {
- // https://trello.com/c/c8v321Xx/160-prompt-user-to-save-changes
- //const snapshot = alt.takeSnapshot();
- //window.localStorage.setItem('composer', snapshot);
- if (this.state.unsavedChanges) {
- return 'You have unsaved changes. If you do not onboard (or update) your changes they will be lost.';
- }
- }
+ <i className="corner-accent top left" />
+ <i className="corner-accent top right" />
+ <i className="corner-accent bottom left" />
+ <i className="corner-accent bottom right" />
+ {AppHeader}
+ <div className="AppBody">
+ <div className={classNames}>
+ <CatalogPanel layout={self.state.layout}
+ isLoading={isLoading}
+ hasNoCatalogs={hasNoCatalogs}
+ filterByType={self.state.filterCatalogByTypeValue}
+ rbacDisabled={rbacDisabled} />
+ <CanvasPanel layout={self.state.layout}
+ hasNoCatalogs={hasNoCatalogs}
+ showMore={self.state.showMore}
+ containers={containers}
+ title={canvasTitle}
+ zoom={self.state.zoom}
+ panelTabShown={self.state.panelTabShown}
+ files={self.state.files}
+ filesState={self.state.filesState}
+ newPathName={self.state.newPathName}
+ item={self.state.item}
+ type={self.state.filterCatalogByTypeValue}
+ rbacDisabled={rbacDisabled}
+ User={User}
+ />
+ {
+ (self.state.panelTabShown == 'descriptor') ?
+ <DetailsPanel layout={self.state.layout}
+ hasNoCatalogs={hasNoCatalogs}
+ showMore={self.state.showMore}
+ containers={containers}
+ showHelp={self.state.showHelp}
+ collapsePanelsByDefault={self.state.collapsePanelsByDefault}
+ openPanelsWithData={self.state.openPanelsWithData}
+ showJSONViewer={self.state.showJSONViewer} />
+ : null
+ }
+ <ComposerAppToolbar layout={self.state.layout}
+ showMore={self.state.showMore}
+ isEditingNSD={isEditingNSD}
+ isEditingVNFD={isEditingVNFD}
+ isModified={isModified}
+ isNew={isNew}
+ disabled={!hasItem || rbacDisabled}
+ onClick={event => event.stopPropagation()}
+ panelTabShown={self.state.panelTabShown} />
+ </div>
+ </div>
+ <ModalOverlay />
+ </div>
+ );
+ }
+ return html;
+ },
+ onChange(state) {
+ this.setState(state);
+ },
+ onCatalogDataChanged(catalogDataState) {
+ this.setState({
+ isLoading: catalogDataState.isLoading
+ });
+ }
});
+
export default ComposerApp;
const style = {left: this.props.layout.left};
const saveClasses = ClassNames('ComposerAppSave', {'primary-action': this.props.isModified || this.props.isNew});
const cancelClasses = ClassNames('ComposerAppCancel', {'secondary-action': this.props.isModified});
- if (this.props.disabled) {
- return (
- <div className="ComposerAppToolbar" style={style}></div>
- );
- }
+ let isDisabled = this.props.disabled;
+ // console.log('rbacDisabled', isDisabled )
const hasSelection = SelectionManager.getSelections().length > 0;
if(this.props.panelTabShown != 'descriptor') {
style.pointerEvents = 'none';
if (this.props.isEditingNSD || this.props.isEditingVNFD) {
return (
<div className="FileActions">
- <Button className={saveClasses} onClick={this.onClickSave} label={messages.getSaveActionLabel(this.props.isNew)} src={imgSave} />
- <Button className={cancelClasses} onClick={this.onClickCancel} label="Cancel" src={imgCancel} />
- <Button className="ComposerAppToggleJSONViewerAction" onClick={this.toggleJSONViewer} label="YAML Viewer" src={imgJSONViewer} />
+ <Button className={saveClasses} onClick={this.onClickSave} label={messages.getSaveActionLabel(this.props.isNew)} src={imgSave} disabled={isDisabled} />
+ <Button className={cancelClasses} onClick={this.onClickCancel} label="Cancel" src={imgCancel} disabled={isDisabled} />
+ <Button className="ComposerAppToggleJSONViewerAction" onClick={this.toggleJSONViewer} label="YAML Viewer" src={imgJSONViewer} disabled={isDisabled} />
</div>
);
}
})()}
<div className="LayoutActions">
- <Button className="action-auto-layout" onClick={this.onClickAutoLayout} label="Auto Layout" src={imgLayout} />
+ <Button className="action-auto-layout" onClick={this.onClickAutoLayout} label="Auto Layout" src={imgLayout} disabled={isDisabled} />
{this.props.isEditingNSD ||
this.props.isEditingVNFD ? <Button className="action-add-vld"
+ disabled={isDisabled}
draggable="true"
label={this.props.isEditingNSD ? 'Add VLD' : 'Add IVLD'}
src={imgVLD}
onDragStart={this.onDragStartAddVld}
onClick={this.onClickAddVld} /> : null}
{this.props.isEditingNSD ? <Button className="action-add-vnffg"
+ disabled={isDisabled}
draggable="true"
label="Add VNFFG"
src={imgFG}
onDragStart={this.onDragStartAddVnffg}
onClick={this.onClickAddVnffg} /> : null}
{this.props.isEditingVNFD ? <Button className="action-add-vdu"
+ disabled={isDisabled}
draggable="true"
label="Add VDU"
src={imgVDU}
onDragStart={this.onDragStartAddVdu}
onClick={this.onClickAddVdu} /> : null}
- <Button type="image" title="Delete selected items" className="action-delete-selected-items" disabled={!hasSelection} onClick = {this.onClickDeleteSelected} src={imgDelete} label="Delete" />
+ <Button type="image" title="Delete selected items" className="action-delete-selected-items" disabled={!hasSelection || isDisabled} onClick = {this.onClickDeleteSelected} src={imgDelete} label="Delete" />
</div>
</div>
);
--- /dev/null
+
+/*
+ *
+ * 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.
+ *
+ */
+
+
+'use strict';
+
+import _ from 'lodash'
+import d3 from 'd3'
+import React from 'react'
+import Range from '../Range'
+import Button from '../Button'
+import ClassNames from 'classnames'
+import changeCase from 'change-case'
+import LayoutRow from '../LayoutRow'
+import SelectionManager from '../../libraries/SelectionManager'
+import PureRenderMixin from 'react-addons-pure-render-mixin'
+import CatalogItemsActions from '../../actions/CatalogItemsActions'
+import CanvasEditorActions from '../../actions/CanvasEditorActions'
+import DescriptorModelFactory from '../../libraries/model/DescriptorModelFactory'
+import ComposerAppActions from '../../actions/ComposerAppActions'
+import DescriptorModelMetaFactory from '../../libraries/model/DescriptorModelMetaFactory'
+import ComposerAppStore from '../../stores/ComposerAppStore'
+import DeletionManager from '../../libraries/DeletionManager'
+import ContentEditableDiv from '../ContentEditableDiv'
+import TooltipManager from '../../libraries/TooltipManager'
+import HighlightRecordServicePaths from '../../libraries/graph/HighlightRecordServicePaths'
+
+import '../../styles/EditForwardingGraphPaths.scss'
+
+import imgNSD from '../../images/default-catalog-icon.svg'
+import imgFG from '../../../../node_modules/open-iconic/svg/infinity.svg'
+import imgRemove from '../../../../node_modules/open-iconic/svg/trash.svg'
+import imgAdd from '../../../../node_modules/open-iconic/svg/plus.svg'
+import imgConnection from '../../../../node_modules/open-iconic/svg/random.svg'
+import imgClassifier from '../../../../node_modules/open-iconic/svg/spreadsheet.svg'
+import imgReorder from '../../../../node_modules/open-iconic/svg/menu.svg'
+import CatalogDataStore from '../../stores/CatalogDataStore'
+import utils from '../../libraries/utils'
+import getEventPath from '../../libraries/getEventPath'
+import guid from '../../libraries/guid'
+
+import '../../styles/EditDescriptorModelProperties.scss'
+import '../../styles/EditConfigParameterMap.scss';
+
+function configParameterMapMap(ap, i) {
+
+ const context = this;
+ context.vnfapMap = ap;
+ return (
+ <div key={i}>
+ <div>{ap.id}</div>
+ <div>{ap.capability['member-vnf-index']}</div>
+ <div>{ap.capability['capability-ref']}</div>
+
+ </div>
+ )
+
+}
+
+function mapNSD(nsd, i) {
+
+ const context = this;
+ context.nsd = nsd;
+
+ function onClickAddConfigParameterMap(nsd, event) {
+ event.preventDefault();
+ nsd.createConfigParameterMap();
+ CatalogItemsActions.catalogItemDescriptorChanged(nsd.getRoot());
+ }
+
+ const forwardingGraphs = nsd.configParameterMap.map(configParameterMap.bind(context));
+ if (forwardingGraphs.length === 0) {
+ forwardingGraphs.push(
+ <div key="1" className="welcome-message">
+ No Forwarding Graphs to model.
+ </div>
+ );
+ }
+
+ return (
+ <div key={i} className={nsd.className}>
+ {forwardingGraphs}
+ <div className="footer-actions">
+ <div className="row-action-column">
+ <Button className="create-new-forwarding-graph" src={imgAdd} width="20px" onClick={onClickAddConfigParameterMap.bind(null, nsd)} label="Add new Access Point" />
+ </div>
+ </div>
+ </div>
+ );
+
+}
+
+
+function startEditing() {
+ event.stopPropagation();
+ DeletionManager.removeEventListeners();
+ }
+
+function endEditing() {
+ DeletionManager.addEventListeners();
+}
+
+
+const ConfigPrimitiveParameters = React.createClass({
+ mixins: [PureRenderMixin],
+ getInitialState: function () {
+ return ComposerAppStore.getState();
+ },
+ getDefaultProps: function () {
+ return {
+ containers: []
+ };
+ },
+ componentWillMount: function () {
+ },
+ componentDidMount: function () {
+ },
+ componentDidUpdate: function () {
+ },
+ componentWillUnmount: function () {
+ },
+ render() {
+ const self = this;
+ const containers = this.props.containers;
+ let NSContainer = containers.filter(function(c) {
+ return c.className == "NetworkService"
+ })[0]
+ const context = {
+ component: this,
+ containers: containers
+ };
+
+ const networkService = containers.filter(d => d.type === 'nsd');
+ if (networkService.length === 0) {
+ return <p className="welcome-message">No <img src={imgNSD} width="20px" /> NSD open in the canvas. Try opening an NSD.</p>;
+ }
+ let MapData = constructRequestSourceData(containers);
+ let mapCounter = 1;
+
+
+
+ return (
+ <div className="ConfigParameterMap">
+
+ <div className="config-parameter-map">
+ <div className="config-parameter-titles">
+ <div className="config-parameter">
+ Primitive Parameter Request
+ </div>
+ <div className="config-parameter">
+ Data Source
+ </div>
+ </div>
+ {
+ MapData.Requests.map(function(r, i) {
+ let currentValue = {};
+ let SourceOptions = [<option value={JSON.stringify({
+ requestValue: r.name,
+ requestIndex: r.vnfdIndex
+ })} key="reset">No Source Selected</option>]
+ MapData.Sources.map(function(s, j) {
+ let value = {
+ value: s.name,
+ index: s.vnfdIndex,
+ requestValue: r.name,
+ requestIndex: r.vnfdIndex
+ }
+ if (r.vnfdIndex !== s.vnfdIndex) {
+ SourceOptions.push(<option value={JSON.stringify(value)} key={`${j}-${i}`} >{`${s.vnfdName} (${s.vnfdIndex}): ${s.name}`}</option>)
+ }
+ })
+ //Finds current value
+ NSContainer.model['config-parameter-map'] && NSContainer.model['config-parameter-map'].map((c)=>{
+ if(
+ c['config-parameter-request'] &&
+ (c['config-parameter-request']['config-parameter-request-ref'] == r.name)
+ && (c['config-parameter-request']['member-vnf-index-ref'] == r.vnfdIndex)
+ ) {
+ currentValue = {
+ value: c['config-parameter-source']['config-parameter-source-ref'],
+ index: c['config-parameter-source']['member-vnf-index-ref'],
+ requestValue: r.name,
+ requestIndex: r.vnfdIndex
+ };
+ }
+ })
+ currentValue.hasOwnProperty('value') ? mapCounter++ : mapCounter--;
+ let currentMapIndex = (mapCounter > 0) ? (mapCounter) - 1: 0;
+ return (
+ <div key={i} className="EditDescriptorModelProperties -is-tree-view config-parameter config-parameter-group">
+ <div className="config-parameter-request" >{`${r.vnfdName} (${r.vnfdIndex}): ${r.name}`}</div>
+ <div className="config-parameter-source">
+ <select
+ onChange={onFormFieldValueChanged.bind(NSContainer, i)}
+ onBlur={endEditing}
+ onMouseDown={startEditing}
+ onMouseOver={startEditing}
+ value={JSON.stringify(currentValue)}
+ >
+ }
+ {SourceOptions}
+ </select>
+ </div>
+ </div>
+ )
+ })
+ }
+ </div>
+ </div>
+ )
+ }
+});
+
+ function onFormFieldValueChanged(index, event) {
+ if (DescriptorModelFactory.isContainer(this)) {
+ event.preventDefault();
+ const name = event.target.name;
+ const value = JSON.parse(event.target.value);
+
+ let ConfigMap = utils.resolvePath(this.model, 'config-parameter-map');
+ let ConfigMapIndex = false;
+ let id = guid().substring(0, 8);
+ //Check current map, if request is present, assign map index.
+ ConfigMap.map(function(c, i) {
+ let req = c['config-parameter-request'];
+ if((req['config-parameter-request-ref'] == value.requestValue) &&
+ (req['member-vnf-index-ref'] == value.requestIndex)) {
+ ConfigMapIndex = i;
+ id = c.id;
+ }
+ });
+ if(!ConfigMapIndex && _.isBoolean(ConfigMapIndex)) {
+ ConfigMapIndex = ConfigMap.length;
+ }
+ if(value.value) {
+ utils.assignPathValue(this.model, 'config-parameter-map.' + ConfigMapIndex + '.config-parameter-source.config-parameter-source-ref', value.value);
+ utils.assignPathValue(this.model, 'config-parameter-map.' + ConfigMapIndex + '.config-parameter-source.member-vnf-index-ref', value.index);
+ utils.assignPathValue(this.model, 'config-parameter-map.' + ConfigMapIndex + '.config-parameter-request.config-parameter-request-ref', value.requestValue);
+ utils.assignPathValue(this.model, 'config-parameter-map.' + ConfigMapIndex + '.config-parameter-request.member-vnf-index-ref', value.requestIndex);
+ utils.assignPathValue(this.model, 'config-parameter-map.' + ConfigMapIndex + '.id', id);
+ CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+ } else {
+ utils.removePathValue(this.model, 'config-parameter-map.' + ConfigMapIndex)
+ CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+
+ }
+ }
+ }
+
+
+//Values from
+//
+
+//To update
+//Container:NSD
+//path
+//["config-parameter", "config-parameter-source"]
+//{config-parameter-source-ref: "service_port", member-vnf-index-ref: 2}
+
+function constructRequestSourceData(containers) {
+ let cds = CatalogDataStore;
+ let catalogs = cds.getTransientCatalogs();
+ let Requests = [];
+ let Sources = [];
+ let vnfdData = {
+ index:[],
+ vnfdIDs:[],
+ indexRefs: {},
+ vnfdRefs:{}
+ };
+
+ //Init VNFD map
+ //{
+ //
+ // index:[1], //member-vnfd-index-ref
+ // vnfdIDs:[],
+ // indexRefs: {
+ // 1: vnfdID
+ // },
+ // vnfdRefs: {
+ // {1.id} : {...}
+ // }
+ //}
+
+ containers.map(function(c, i) {
+ if(c.className == 'ConstituentVnfd') {
+ vnfdData.index.push(c.vnfdIndex);
+ vnfdData.vnfdIDs.push(c.vnfdId);
+ vnfdData.indexRefs[c.vnfdIndex] = c.vnfdId;
+ vnfdData.vnfdRefs[c.vnfdId] = {
+ id: c.vnfdId,
+ name: c.name,
+ 'short-name': c['short-name']
+ };
+ }
+ });
+
+ //Decorate VNFDMap with descriptor data;
+ catalogs[1].descriptors
+ .filter((v) => vnfdData.vnfdIDs.indexOf(v.id) > -1)
+ .map(constructVnfdMap.bind(this, vnfdData));
+
+
+ vnfdData.index.map(function(vnfdIndex) {
+ let vnfdId = vnfdData.indexRefs[vnfdIndex];
+ let vnfd = vnfdData.vnfdRefs[vnfdId];
+ let vnfdShortName = vnfd['short-name'];
+ vnfd.requests && vnfd.requests.map(function(request) {
+ Requests.push(_.merge({
+ id: vnfdId,
+ vnfdIndex: vnfdIndex,
+ vnfdName: vnfdShortName,
+ }, request))
+ });
+ vnfd.sources && vnfd.sources.map(function(source) {
+ Sources.push(_.merge({
+ id: vnfdId,
+ vnfdIndex: vnfdIndex,
+ vnfdName: vnfdShortName,
+ }, source));
+ });
+ })
+
+ return {Requests, Sources};
+
+ function constructVnfdMap(vnfdData, vnfd) {
+ let data = {
+ requests: vnfd['config-parameter'] && vnfd['config-parameter']['config-parameter-request'],
+ sources: vnfd['config-parameter'] && vnfd['config-parameter']['config-parameter-source']
+ };
+ vnfdData.vnfdRefs[vnfd.id] = _.merge(vnfdData.vnfdRefs[vnfd.id], data);
+ }
+
+}
+
+
+
+export default ConfigPrimitiveParameters;
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
'use strict';
import _cloneDeep from 'lodash/cloneDeep'
+import _isArray from 'lodash/isArray'
+import _isObject from 'lodash/isObject'
+import _keys from 'lodash/keys'
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin'
import messages from './messages'
import serializers from '../libraries/model/DescriptorModelSerializer'
import JSONViewer from 'widgets/JSONViewer/JSONViewer';
import PopupWindow from './PopupWindow'
+import DetailsPanelToolbar from './DetailsPanelToolbar'
+import NavigateDescriptorModel from './NavigateDescriptorModel'
+import NavigateDescriptorErrors from './NavigateDescriptorErrors'
import CatalogItemDetailsEditor from './CatalogItemDetailsEditor'
import SelectionManager from '../libraries/SelectionManager'
import '../styles/DetailsPanel.scss'
+function checkForErrors(errors) {
+ return _keys(errors).reduce((inError, k) => {
+ function traverseObject(obj, key) {
+ const node = obj[key];
+ if (_isArray(node)) {
+ return node.reduce((inError, v, i) => {
+ if (!inError && v) {
+ return _keys(v).reduce((inError, k) => {
+ return inError || traverseObject(v, k);
+ }, false);
+ }
+ return inError;
+ }, false);
+ } else if (_isObject(node)) {
+ return _keys(node).reduce((inError, k) => {
+ return inError || traverseObject(node, k);
+ }, false);
+ } else {
+ return !! node;
+ }
+ }
+ return inError || traverseObject(errors, k);
+ }, false);
+}
+
const DetailsPanel = React.createClass({
mixins: [PureRenderMixin, SelectionManager.reactPauseResumeMixin],
getInitialState() {
};
},
componentWillMount() {
+ setTimeout(() => {
+ const height = this.panel && this.panel.offsetHeight;
+ this.setState({ height });
+ }, 100);
},
componentDidMount() {
},
},
componentWillUnmount() {
},
+ contextTypes: {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+ },
+ componentWillUpdate(nextProps) {
+ if ((nextProps.layout != this.props.layout)
+ && (nextProps.layout.height != this.props.layout.height)) {
+ this.componentWillMount();
+ }
+ },
+
render() {
let json = '{}';
- let bodyComponent = messages.detailsWelcome();
+ let bodyContent = this.props.hasNoCatalogs ? null : messages.detailsWelcome();
const selected = this.props.containers.filter(d => SelectionManager.isSelected(d));
const selectedContainer = selected[0];
+ let workingHeight = this.state.height || 1;
+
+ function makeId(container, path) {
+ let idParts = [_isArray(path) ? path.join(':') : path];
+ idParts.push(container.uid);
+ while (container.parent) {
+ container = container.parent;
+ idParts.push(container.uid);
+ }
+ return idParts.reverse().join(':');
+ }
+
if (selectedContainer) {
- bodyComponent = <CatalogItemDetailsEditor container={selectedContainer} width={this.props.layout.right} />;
+ bodyContent = [];
+ bodyContent.push(
+ <DetailsPanelToolbar
+ key='toolbar'
+ container={selectedContainer}
+ showHelp={this.props.showHelp.forAll}
+ width={this.props.layout.right} />
+ );
+ workingHeight -= 32 + 35;
+ bodyContent.push(
+ <NavigateDescriptorModel key='navigate' container={selectedContainer} idMaker={makeId}
+ style={{ margin: '8px 8px 15px' }} />
+ )
+ workingHeight -= 8 + 15 + 37;
+ if (checkForErrors(selectedContainer.uiState.error)) {
+ bodyContent.push(
+ <NavigateDescriptorErrors key='errors' container={selectedContainer} idMaker={makeId}
+ style={{ margin: '8px 8px 15px' }} />
+ )
+ workingHeight -= 8 + 15 + 37;
+ }
+ bodyContent.push(
+ <div key='editor' className="DetailsPanelBody">
+ <CatalogItemDetailsEditor
+ container={selectedContainer}
+ idMaker={makeId}
+ showHelp={this.props.showHelp}
+ collapsePanelsByDefault={this.props.collapsePanelsByDefault}
+ openPanelsWithData={this.props.openPanelsWithData}
+ width={this.props.layout.right}
+ height={workingHeight} />
+ </div>
+ );
const edit = _cloneDeep(selectedContainer.model);
json = serializers.serialize(edit) || edit;
}
const jsonViewerTitle = selectedContainer ? selectedContainer.model.name : 'nothing selected';
- const hasNoCatalogs = this.props.hasNoCatalogs;
return (
- <div className="DetailsPanel" data-resizable="left" data-resizable-handle-offset="0 5" style={{width: this.props.layout.right}} onClick={event => event.preventDefault()}>
- <div className="DetailsPanelBody">
- {hasNoCatalogs ? null : bodyComponent}
- </div>
- <PopupWindow show={this.props.showJSONViewer} title={jsonViewerTitle}><JSONViewer json={json}/></PopupWindow>
+ <div ref={el => this.panel = el} className="DetailsPanel" data-resizable="left" data-resizable-handle-offset="0 5" style={{ width: this.props.layout.right }} onClick={event => event.preventDefault()}>
+ {bodyContent}
+ <PopupWindow show={this.props.showJSONViewer} title={jsonViewerTitle}><JSONViewer json={json} /></PopupWindow>
</div>
);
}
--- /dev/null
+
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react'
+import Button from './Button'
+import DescriptorEditorActions from '../actions/DescriptorEditorActions'
+
+import '../styles/DetailsPanelToolbar.scss'
+
+import imgQuestionMark from '../../../node_modules/open-iconic/svg/question-mark.svg'
+import imgExpandAll from '../../../node_modules/open-iconic/svg/caret-right.svg'
+import imgCollapseAll from '../../../node_modules/open-iconic/svg/caret-bottom.svg'
+import imgCollapseSome from '../../../node_modules/open-iconic/svg/elevator.svg'
+
+export default function (props) {
+ const { container, showHelp } = props;
+ function onClickAction(action, event) {
+ action({ descriptor: container });
+ }
+ return (
+ <div className="DetailsPanelToolbar">
+ <div className="btn-bar">
+ <div className="btn-group" style={{ display: 'inline-block' }}>
+ <Button type="image" title="Expand all" className="action-onboard-catalog-package"
+ onClick={onClickAction.bind(null, DescriptorEditorActions.expandAllPanels)} src={imgExpandAll} />
+ <Button type="image" title="Collapse all" className="action-update-catalog-package"
+ onClick={onClickAction.bind(null, DescriptorEditorActions.collapseAllPanels)} src={imgCollapseAll} />
+ <Button type="image" title="Collapse only data-less panels" className="action-update-catalog-package"
+ onClick={onClickAction.bind(null, DescriptorEditorActions.showPanelsWithData)} src={imgCollapseSome} />
+ </div>
+ <div className="btn-group" style={{ display: 'inline-block' }}>
+ <Button
+ type="image"
+ label={showHelp ? "Hide" : "Show"}
+ title={showHelp ? "Hide descriptions" : "Show descriptions"}
+ className="action-onboard-catalog-package"
+ src={imgQuestionMark}
+ onClick={onClickAction.bind(null,
+ showHelp ? DescriptorEditorActions.showHelpForNothing
+ : DescriptorEditorActions.showHelpForAll)}
+ />
+ </div>
+ </div>
+ </div>
+ );
+}
+
* This class generates the form fields used to edit the CONFD JSON model.
*/
-import _includes from 'lodash/includes'
-import _isArray from 'lodash/isArray'
-import _cloneDeep from 'lodash/cloneDeep'
-import _debounce from 'lodash/debounce';
import _uniqueId from 'lodash/uniqueId';
import _set from 'lodash/set';
import _get from 'lodash/get';
import _has from 'lodash/has';
+import _keys from 'lodash/keys';
+import _isObject from 'lodash/isObject';
+import _isArray from 'lodash/isArray';
import utils from '../libraries/utils'
import React from 'react'
-import ClassNames from 'classnames'
import changeCase from 'change-case'
import toggle from '../libraries/ToggleElementHandler'
-import Button from './Button'
import Property from '../libraries/model/DescriptorModelMetaProperty'
+import SelectionManager from '../libraries/SelectionManager'
import ComposerAppActions from '../actions/ComposerAppActions'
import CatalogItemsActions from '../actions/CatalogItemsActions'
-import DESCRIPTOR_MODEL_FIELDS from '../libraries/model/DescriptorModelFields'
+import DescriptorEditorActions from '../actions/DescriptorEditorActions'
import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
-import SelectionManager from '../libraries/SelectionManager'
-import DeletionManager from '../libraries/DeletionManager'
-import DescriptorModelIconFactory from '../libraries/model/IconFactory'
-import getEventPath from '../libraries/getEventPath'
-import CatalogDataStore from '../stores/CatalogDataStore'
-import imgAdd from '../../../node_modules/open-iconic/svg/plus.svg'
-import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg'
+import ModelBreadcrumb from './model/ModelBreadcrumb'
+import ListItemAsLink from './model/ListItemAsLink'
+import LeafField from './model/LeafField'
+import { List, ListItem } from './model/List'
+import ContainerWrapper from './model/Container'
+import Choice from './model/Choice'
import '../styles/EditDescriptorModelProperties.scss'
-const EMPTY_LEAF_PRESENT = '--empty-leaf-set--';
function resolveReactKey(value) {
- const keyPath = ['uiState', 'fieldKey'];
+ const keyPath = ['uiState', 'fieldKey'];
if (!_has(value, keyPath)) {
_set(value, keyPath, _uniqueId());
}
return _get(value, keyPath);
}
-function getDescriptorMetaBasicForType(type) {
- const basicPropertiesFilter = d => _includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
- return DescriptorModelMetaFactory.getModelMetaForType(type, basicPropertiesFilter) || {properties: []};
+function getTipForProperty(property) {
+ return property.name === 'constituent-vnfd' ? "Drag a VNFD from the Catalog to add more." : null
}
-function getDescriptorMetaAdvancedForType(type) {
- const advPropertiesFilter = d => !_includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
- return DescriptorModelMetaFactory.getModelMetaForType(type, advPropertiesFilter) || {properties: []};
+function selectModel(container, model, property) {
+ ComposerAppActions.selectModel(container.findChildByUid(model));
}
-function getTitle(model = {}) {
- if (typeof model['short-name'] === 'string' && model['short-name']) {
- return model['short-name'];
- }
- if (typeof model.name === 'string' && model.name) {
- return model.name;
- }
- if (model.uiState && typeof model.uiState.displayName === 'string' && model.uiState.displayName) {
- return model.uiState.displayName
- }
- if (typeof model.id === 'string') {
- return model.id;
- }
+function removeListEntry(container, property, path) {
+ DescriptorEditorActions.removeListItem({ descriptor: container, property, path });
}
-export default function EditDescriptorModelProperties(props) {
-
- const container = props.container;
-
- if (!(DescriptorModelFactory.isContainer(container))) {
- return
- }
-
- function startEditing() {
- DeletionManager.removeEventListeners();
- }
-
- function endEditing() {
- DeletionManager.addEventListeners();
- }
-
- function onClickSelectItem(property, path, value, event) {
- event.preventDefault();
- const root = this.getRoot();
- if (SelectionManager.select(value)) {
- CatalogItemsActions.catalogItemMetaDataChanged(root.model);
- }
- }
-
- function onFocusPropertyFormInputElement(property, path, value, event) {
-
- event.preventDefault();
- startEditing();
-
- function removeIsFocusedClass(event) {
- event.target.removeEventListener('blur', removeIsFocusedClass);
- Array.from(document.querySelectorAll('.-is-focused')).forEach(d => d.classList.remove('-is-focused'));
- }
-
- removeIsFocusedClass(event);
+function createAndAddItemToPath(container, property, path) {
+ DescriptorEditorActions.addListItem({ descriptor: container, property, path });
+}
- const propertyWrapper = getEventPath(event).reduce((parent, element) => {
- if (parent) {
- return parent;
- }
- if (!element.classList) {
- return false;
- }
- if (element.classList.contains('property')) {
- return element;
- }
- }, false);
+function notifyPropertyFocused(container, path) {
+ container.getRoot().uiState.focusedPropertyPath = path.join('.');
+ console.debug('property selected', path.join('.'));
+ ComposerAppActions.propertySelected([path.join('.')]);
+}
- if (propertyWrapper) {
- propertyWrapper.classList.add('-is-focused');
- event.target.addEventListener('blur', removeIsFocusedClass);
- }
+function setPropertyOpenState(container, path, property, isOpen) {
+ DescriptorEditorActions.setOpenState({ descriptor: container, property, path, isOpen });
+}
- }
+function isDataProperty(property) {
+ return property.type === 'leaf' || property.type === 'leaf_list' || property.type === 'choice';
+}
- function buildAddPropertyAction(container, property, path) {
- function onClickAddProperty(property, path, event) {
- event.preventDefault();
- //SelectionManager.resume();
- const create = Property.getContainerCreateMethod(property, this);
- if (create) {
- const model = null;
- create(model, path, property);
- } else {
- const name = path.join('.');
- // get a unique name for the new list item based on the current list content
- // some lists, based on the key, may not get a uniqueName generated here
- const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(container.model[property.name], property);
- const value = Property.createModelInstance(property, uniqueName);
- utils.assignPathValue(this.model, name, value);
- }
- CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+function checkIfValueEmpty(value) {
+ if (value === null || typeof value === 'undefined') {
+ return true;
+ } else if (_isArray(value) && !value.length) {
+ return true;
+ } else if (_isObject(value)) {
+ const keys = _keys(value);
+ if (keys.length < 2) {
+ return !keys.length || (keys[0] === 'uiState')
}
- return (
- <Button className="inline-hint" onClick={onClickAddProperty.bind(container, property, path)} label="Add" src={imgAdd} />
- );
}
+ return false;
+}
- function buildRemovePropertyAction(container, property, path) {
- function onClickRemoveProperty(property, path, event) {
- event.preventDefault();
- const name = path.join('.');
- const removeMethod = Property.getContainerMethod(property, this, 'remove');
- if (removeMethod) {
- removeMethod(utils.resolvePath(this.model, name));
- } else {
- utils.removePathValue(this.model, name);
- }
- CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+export default function EditDescriptorModelProperties(props) {
+ const { container, idMaker, showHelp, collapsePanelsByDefault, openPanelsWithData } = props;
+ const readOnly = props.readOnly || container.isReadOnly;
+ const showElementHelp = showHelp.forAll;
+ const uiState = container.uiState;
+
+ function getPanelOpenedCondition(value, path) {
+ const showOpened = container.getUiState('opened', path);
+ if (typeof showOpened === 'undefined') {
+ return (openPanelsWithData && !checkIfValueEmpty(value)) ? true : !collapsePanelsByDefault;
}
- return (
- <Button className="remove-property-action inline-hint" title="Remove" onClick={onClickRemoveProperty.bind(container, property, path)} label="Remove" src={imgRemove}/>
- );
+ return showOpened;
}
- function buildField(container, property, path, value, fieldKey) {
- let cds = CatalogDataStore;
- let catalogs = cds.getTransientCatalogs();
-
+ function buildField(property, path, value, fieldKey) {
const pathToProperty = path.join('.');
- const isEditable = true;
- const isGuid = Property.isGuid(property);
- const isBoolean = Property.isBoolean(property);
- const isEnumeration = Property.isEnumeration(property);
- const isLeafRef = Property.isLeafRef(property);
- const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
- const placeholder = changeCase.title(property.name);
- const className = ClassNames(property.name + '-input', {'-is-guid': isGuid});
const fieldValue = value ? (value.constructor.name != "Object") ? value : '' : (isNaN(value) ? undefined : value);
// process the named field value change
function processFieldValueChange(name, value) {
console.debug('processed change for -- ' + name + ' -- with value -- ' + value);
- // this = the container being edited
- if (DescriptorModelFactory.isContainer(this)) {
- utils.assignPathValue(this.model, name, value);
- CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
- }
- }
-
- // change handler used for onChange event
- const changeHandler = (handleValueChange, event) => {
- event.preventDefault();
- console.debug(event.target.value);
- handleValueChange(event.target.value);
- };
- // create an onChange event handler for a text field for the specified field path (debounced to accumulate chars)
- const onTextChange = changeHandler.bind(null, _debounce(
- processFieldValueChange.bind(container, pathToProperty), 2000, {maxWait: 5000})); // max wait for short-name
- // create an onChange event handler for a select field for the specified field path
- const onSelectChange = changeHandler.bind(null, processFieldValueChange.bind(container, pathToProperty));
-
- if (isEnumeration) {
- const enumeration = Property.getEnumeration(property, value);
- const options = enumeration.map((d, i) => {
- // note yangforge generates values for enums but the system does not use them
- // so we categorically ignore them
- // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
- //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
- return <option key={':' + i} value={d.name}>{d.name}</option>;
- });
- const isValueSet = enumeration.filter(d => d.isSelected).length > 0;
- if (!isValueSet || property.cardinality === '0..1') {
- const noValueDisplayText = changeCase.title(property.name);
- options.unshift(<option key={'(value-not-in-enum)'} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
- }
- return (
- <select
- key={fieldKey}
- id={fieldKey}
- className={ClassNames({'-value-not-set': !isValueSet})}
- defaultValue={value}
- title={pathToProperty}
- onChange={onSelectChange}
- onFocus={onFocus}
- onBlur={endEditing}
- onMouseDown={startEditing}
- onMouseOver={startEditing}
- readOnly={!isEditable}>
- {options}
- </select>
- );
- }
-
- if (isLeafRef) {
- let fullPathString = container.key + ':' + path.join(':');
- let containerRef = container;
- while (containerRef.parent) {
- fullPathString = containerRef.parent.key + ':' + fullPathString;
- containerRef = containerRef.parent;
- }
- const leafRefPathValues = Property.getLeafRef(property, path, value, fullPathString, catalogs, container);
-
- const options = leafRefPathValues && leafRefPathValues.map((d, i) => {
- return <option key={':' + i} value={d.value}>{d.value}</option>;
- });
- const isValueSet = leafRefPathValues.filter(d => d.isSelected).length > 0;
- if (!isValueSet || property.cardinality === '0..1') {
- const noValueDisplayText = changeCase.title(property.name);
- options.unshift(<option key={'(value-not-in-leafref)'} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
+ if (DescriptorModelFactory.isContainer(container)) {
+ DescriptorEditorActions.setValue({ descriptor: container, path, value });
}
- return (
- <select
- key={fieldKey}
- id={fieldKey}
- className={ClassNames({'-value-not-set': !isValueSet})}
- defaultValue={value}
- title={pathToProperty}
- onChange={onSelectChange}
- onFocus={onFocus}
- onBlur={endEditing}
- onMouseDown={startEditing}
- onMouseOver={startEditing}
- readOnly={!isEditable}>
- {options}
- </select>
- );
- }
-
- if (isBoolean) {
- const options = [
- <option key={'true'} value="TRUE">TRUE</option>,
- <option key={'false'} value="FALSE">FALSE</option>
- ]
-
- // if (!isValueSet) {
- const noValueDisplayText = changeCase.title(property.name);
- options.unshift(<option key={'(value-not-in-leafref)'} value="" placeholder={placeholder}></option>);
- // }
- let val = value;
- if(typeof(val) == 'number') {
- val = value ? "TRUE" : "FALSE"
- }
- const isValueSet = (val != '' && val)
- return (
- <select
- key={fieldKey}
- id={fieldKey}
- className={ClassNames({'-value-not-set': !isValueSet})}
- defaultValue={val && val.toUpperCase()}
- title={pathToProperty}
- onChange={onSelectChange}
- onFocus={onFocus}
- onBlur={endEditing}
- onMouseDown={startEditing}
- onMouseOver={startEditing}
- readOnly={!isEditable}>
- {options}
- </select>
- );
- }
-
- if (Property.isLeafEmpty(property)) {
- // A null value indicates the leaf exists (as opposed to undefined).
- // We stick in a string when the user actually sets it to simplify things
- // but the correct thing happens when we serialize to user data
- let isEmptyLeafPresent = (value === EMPTY_LEAF_PRESENT || value === null);
- let present = isEmptyLeafPresent ? EMPTY_LEAF_PRESENT : "";
- const options = [
- <option key={'true'} value={EMPTY_LEAF_PRESENT}>Enabled</option>,
- <option key={'false'} value="">Not Enabled</option>
- ]
-
- return (
- <select
- key={fieldKey}
- id={fieldKey}
- className={ClassNames({'-value-not-set': !isEmptyLeafPresent})}
- defaultValue={present}
- title={pathToProperty}
- onChange={onSelectChange}
- onFocus={onFocus}
- onBlur={endEditing}
- onMouseDown={startEditing}
- onMouseOver={startEditing}
- readOnly={!isEditable}>
- {options}
- </select>
- );
}
- if (property['preserve-line-breaks']) {
- return (
- <textarea
- key={fieldKey}
- cols="5"
- id={fieldKey}
- defaultValue={value}
- placeholder={placeholder}
- onChange={onTextChange}
- onFocus={onFocus}
- onBlur={endEditing}
- onMouseDown={startEditing}
- onMouseOver={startEditing}
- onMouseOut={endEditing}
- onMouseLeave={endEditing}
- readOnly={!isEditable} />
- );
+ function onErrorHandler(message) {
+ DescriptorEditorActions.setError({ descriptor: container, path, message });
}
+ // create an onChange event handler for a select field for the specified field path
+ const onChangeHandler = processFieldValueChange.bind(null, pathToProperty);
return (
- <input
+ <LeafField
key={fieldKey}
+ container={container}
+ property={property}
+ path={path}
+ value={value}
id={fieldKey}
- type="text"
- defaultValue={fieldValue}
- className={className}
- placeholder={placeholder}
- onChange={onTextChange}
- onFocus={onFocus}
- onBlur={endEditing}
- onMouseDown={startEditing}
- onMouseOver={startEditing}
- onMouseOut={endEditing}
- onMouseLeave={endEditing}
- readOnly={!isEditable}
+ showHelp={showElementHelp}
+ onChange={onChangeHandler}
+ onError={onErrorHandler}
+ readOnly={readOnly}
+ errorMessage={_get(container.uiState, ['error'].concat(path))}
/>
);
-
}
/**
* @param {[property]} properties
* @param {string} pathToProperties path within the container to the properties
* @param {Object} data source for each property
- * @param {any} props object containing main data panel information, e.g. panel width {width: 375}
- * which may be useful/necessary to a components rendering.
* @returns an array of react components
*/
- function buildComponentsForProperties(container, properties, pathToProperties, data, props) {
+ function buildComponentsForProperties(properties, pathToProperties, data) {
return properties.map((property) => {
let value;
let propertyPath = pathToProperties.slice();
+ if (property.type != 'choice') {
+ propertyPath.push(property.name);
+ }
if (data && typeof data === 'object') {
- value = data[property.name];
+ value = _get(data, property.name);
}
- if(property.type != 'choice'){
- propertyPath.push(property.name);
+ let result = null;
+ try {
+ result = buildPropertyComponent(property, propertyPath, value);
+ } catch (e) {
+ console.error(e);
}
- return build(container, property, propertyPath, value, props);
+ return result;
});
}
- function buildElement(container, property, valuePath, value) {
- return buildComponentsForProperties(container, property.properties, valuePath, value);
- }
+ function buildChoice(property, path, value, uniqueId) {
+ const uiStatePath = path.concat(['uiState']);
+ const choiceStatePath = ['choice', property.name];
+ const fullChoiceStatePath = uiStatePath.concat(choiceStatePath);
- function buildChoice(container, property, path, value, key) {
-
- function processChoiceChange(name, value) {
- if (DescriptorModelFactory.isContainer(this)) {
-
- /*
- Transient State is stored for convenience in the uiState field.
- The choice yang type uses case elements to describe the "options".
- A choice can only ever have one option selected which allows
- the system to determine which type is selected by the name of
- the element contained within the field.
- */
- /*
- const stateExample = {
- uiState: {
- choice: {
- 'conf-config': {
- selected: 'rest',
- 'case': {
- rest: {},
- netconf: {},
- script: {}
- }
- }
- }
- }
- };
- */
- const statePath = ['uiState.choice'].concat(name);
- const stateObject = utils.resolvePath(this.model, statePath.join('.')) || {};
- const selected = stateObject.selected ? stateObject.selected.split('.')[1] : undefined;
- // write state back to the model so the new state objects are captured
- utils.assignPathValue(this.model, statePath.join('.'), stateObject);
-
- // write the current choice value into the state
- let choiceObject = utils.resolvePath(this.model, [name, selected].join('.'));
- let isTopCase = false;
- if (!choiceObject) {
- isTopCase = true;
- choiceObject = utils.resolvePath(this.model, [selected].join('.'));
+ function determineSelectedChoice(model) {
+ let choiceState = utils.resolvePath(container.model, fullChoiceStatePath.join('.'));
+ if (choiceState) {
+ return property.properties.find(c => c.name === choiceState.selected);
+ }
+ const selectedCase = property.properties.find(c =>
+ c.properties && c.properties.find(p => _has(model, path.concat([p.name])))
+ );
+ // lets remember this
+ let stateObject = utils.resolvePath(container.model, uiStatePath.join('.'));
+ stateObject = _set(stateObject || {}, choiceStatePath, { selected: selectedCase ? selectedCase.name : "" });
+ utils.assignPathValue(container.model, uiStatePath.join('.'), stateObject);
+ return selectedCase;
+ }
+
+ function pullOutCaseModel(caseName) {
+ const model = container.model;
+ const properties = property.properties.find(c => c.name === caseName).properties;
+ return properties.reduce((o, p) => {
+ const valuePath = path.concat([p.name]).join('.');
+ const value = utils.resolvePath(model, valuePath);
+ if (value) {
+ o[p.name] = value;
}
- utils.assignPathValue(stateObject, [selected].join('.'), _cloneDeep(choiceObject));
+ return o;
+ }, {});
+ }
- if(selected) {
- if(this.model.uiState.choice.hasOwnProperty(name)) {
- delete this.model[selected];
- utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
+ function processChoiceChange(value) {
+ if (DescriptorModelFactory.isContainer(container)) {
+ let uiState = utils.resolvePath(container.model, uiStatePath.join('.'));
+ // const stateObject = utils.resolvePath(container.model, fullChoiceStatePath.join('.')) || {};
+ let choiceState = _get(uiState, choiceStatePath);
+ const previouslySelectedChoice = choiceState.selected;
+ if (previouslySelectedChoice === value) {
+ return;
+ }
+ if (previouslySelectedChoice) {
+ choiceState[previouslySelectedChoice] = pullOutCaseModel(previouslySelectedChoice);
+ }
+ const modelUpdate = _keys(choiceState[previouslySelectedChoice]).reduce((o, k) => _set(o, k, null), {})
+ choiceState.selected = value;
+ _set(uiState, choiceStatePath, choiceState);
+ _set(modelUpdate, 'uiState', uiState);
+ if (choiceState.selected) {
+ const previous = choiceState[choiceState.selected];
+ if (previous) {
+ Object.assign(modelUpdate, previous);
} else {
- // remove the current choice value from the model
- utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
+ const newChoice = property.properties.find(p => p.name === choiceState.selected);
+ if (newChoice.properties.length === 1) {
+ const property = newChoice.properties[0];
+ if (property.type === 'leaf' && property['data-type'] === 'empty') {
+ let obj = {};
+ obj[property.name] = [null];
+ Object.assign(modelUpdate, obj);
+ }
+ }
}
}
-
- // get any state for the new selected choice
- const newChoiceObject = utils.resolvePath(stateObject, [value].join('.')) || {};
-
- // assign new choice value to the model
- if (isTopCase) {
- utils.assignPathValue(this.model, [name, value].join('.'), newChoiceObject);
- } else {
- utils.assignPathValue(this.model, [value].join('.'), newChoiceObject)
- }
-
- // update the selected name
- utils.assignPathValue(this.model, statePath.concat('selected').join('.'), value);
-
- CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+ DescriptorEditorActions.assignValue({ descriptor: container, path, source: modelUpdate });
}
}
- const pathToChoice = path.join('.');
- const caseByNameMap = {};
-
- const choiceChangeHandler = processChoiceChange.bind(container, pathToChoice);
- const onChange = ((handleChoiceChange, event) => {
- event.preventDefault();
- handleChoiceChange(event.target.value);
- }).bind(null, choiceChangeHandler);
-
-
- const cases = property.properties.map(d => {
- if (d.type === 'case') {
- //Previous it was assumed that a case choice would have only one property. Now we pass on either the only item or the
- caseByNameMap[d.name] = d.properties && (d.properties.length == 1 ? d.properties[0] : d.properties);
- return {
- optionName: d.name,
- optionTitle: d.description,
- //represents case name and case element name
- optionValue: [d.name, d.properties[0].name].join('.')
- };
- }
- caseByNameMap[d.name] = d;
- return {optionName: d.name};
- });
+ const selectedCase = determineSelectedChoice(container.model);
+ const children = selectedCase ?
+ <ContainerWrapper property={selectedCase} readOnly={readOnly} showHelp={showElementHelp} showOpened={true}>
+ {buildComponentsForProperties(selectedCase.properties, path, path.length ? _get(container.model, path) : container.model)}
+ </ContainerWrapper>
+ : null;
- const options = [{optionName: '', optionValue: false}].concat(cases).map((d, i) => {
- return (
- <option key={i} value={d.optionValue} title={d.optionTitle}>
- {d.optionName}
- {i ? null : changeCase.title(property.name)}
- </option>
- );
- });
+ return (
+ <Choice key={uniqueId} id={uniqueId} onChange={processChoiceChange} readOnly={readOnly} showHelp={showElementHelp}
+ property={property} value={selectedCase ? selectedCase.name : null}
+ >
+ {children}
+ </Choice>
+ );
- let selectedOptionPath = ['uiState.choice', pathToChoice, 'selected'].join('.');
- //Currently selected choice/case statement on UI model
- let selectedOptionValue = utils.resolvePath(container.model, selectedOptionPath);
- //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
- if(!selectedOptionValue) {
- //get field properties for choice on container model
- let fieldProperties = utils.resolvePath(container.model, pathToChoice);
- if(fieldProperties) {
- //Check each case statement in model and see if it is present in container model.
- cases.map(function(c){
- if(c.optionValue && fieldProperties.hasOwnProperty(c.optionValue.split('.')[1])) {
- utils.assignPathValue(container.model, ['uiState.choice', pathToChoice, 'selected'].join('.'), c.optionValue);
- }
- });
- selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', pathToChoice, 'selected'].join('.'));
- } else {
- property.properties.map(function(p) {
- let pname = p.properties[0] && p.properties[0].name;
- if(container.model.hasOwnProperty(pname)) {
- utils.assignPathValue(container.model, ['uiState.choice', pathToChoice, 'selected'].join('.'), [p.name, pname].join('.'));
- }
- })
- selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', pathToChoice, 'selected'].join('.'));
- }
- }
- //If selectedOptionValue is present, take first item in string which represents the case name.
- const valueProperty = caseByNameMap[selectedOptionValue ? selectedOptionValue.split('.')[0] : undefined] || {properties: []};
- const isLeaf = Property.isLeaf(valueProperty);
- const hasProperties = _isArray(valueProperty.properties) && valueProperty.properties.length;
- const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(valueProperty);
- //Some magic that prevents errors for arising
- let valueResponse = null;
- if (valueProperty.properties && valueProperty.properties.length) {
- valueResponse = valueProperty.properties.map(valuePropertyFn);
- } else if (!isMissingDescriptorMeta) {
- let value = utils.resolvePath(container.model, path.concat(valueProperty.name).join('.')) || container.model[valueProperty.name];
- valueResponse = build(container, valueProperty, path.concat(valueProperty.name), value)
- } else {
- valueResponse = valueProperty.map && valueProperty.map(valuePropertyFn);
+ }
+
+ function buildLeafList(property, path, value, uniqueId) {
+ if (!Array.isArray(value)) {
+ value = [value];
}
- function valuePropertyFn(d, i) {
- const childPath = path.concat(valueProperty.name, d.name);
- const childValue = utils.resolvePath(container.model, childPath.join('.'));
+ const children = value && value.map((v, i) => {
+ let itemPath = path.concat([i]);
+ const field = buildField(property, itemPath, v, uniqueId + i);
return (
- <div key={childPath.concat('info', i).join(':')}>
- {build(container, d, childPath, childValue, props)}
- </div>
- );
- }
- // end magic
- const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
-
+ <ListItem key={':' + i} index={i} property={property} readOnly={readOnly} showHelp={showElementHelp}
+ showOpened={true} removeItemHandler={removeListEntry.bind(null, container, property, itemPath)} >
+ {field}
+ </ListItem>
+ )
+ });
return (
- <div key={key} className="choice">
- <select
- key={Date.now()}
- className={ClassNames({'-value-not-set': !selectedOptionValue})}
- defaultValue={selectedOptionValue}
- onChange={onChange}
- onFocus={onFocus}
- onBlur={endEditing}
- onMouseDown={startEditing}
- onMouseOver={startEditing}
- onMouseOut={endEditing}
- onMouseLeave={endEditing}
- >
- {options}
- </select>
- {valueResponse}
- </div>
+ <List key={uniqueId} id={uniqueId} property={property} value={value} readOnly={readOnly} showHelp={showElementHelp}
+ showOpened={true} addItemHandler={createAndAddItemToPath.bind(null, container, property, path)}>
+ {children}
+ </List>
);
-
}
- function buildSimpleListItem(container, property, path, value, uniqueId, index) {
- // todo need to abstract this better
- const title = getTitle(value);
- var req = require.context("../", true, /\.svg/);
+ function buildList(property, path, value, uniqueId) {
+ if (value && !Array.isArray(value)) {
+ value = [value];
+ }
+ function getListItemSummary(index, value) {
+ const keys = property.key.map((key) => value[key]);
+ const summary = keys.join(' ');
+ return summary.length > 1 ? summary : '' + (index + 1);
+ }
+ const children = value && value.map((itemValue, i) => {
+ const itemPath = path.concat([i]);
+ const key = resolveReactKey(itemValue);
+ const children = buildComponentsForProperties(property.properties, itemPath, itemValue);
+ const showOpened = getPanelOpenedCondition(value, itemPath);
+ return (
+ <ListItem key={key} property={property} readOnly={readOnly} showHelp={showElementHelp}
+ summary={getListItemSummary(i, itemValue)} info={'' + (i + 1)}
+ removeItemHandler={removeListEntry.bind(null, container, property, itemPath)}
+ showOpened={showOpened} onChangeOpenState={setPropertyOpenState.bind(null, container, itemPath, property, !showOpened)}>
+ {children}
+ </ListItem>
+ )
+ });
+ const showOpened = getPanelOpenedCondition(value, path);
return (
- <div key={uniqueId} >
- <a href="#select-list-item" className={property.name + '-list-item simple-list-item '} onClick={onClickSelectItem.bind(container, property, path, value)}>
- <img src={req('./' + DescriptorModelIconFactory.getUrlForType(property.name))} width="20px" />
- <span>{title}</span>
- </a>
- {buildRemovePropertyAction(container, property, path)}
- </div>
+ <List key={uniqueId} id={uniqueId} property={property} value={value} readOnly={readOnly} showHelp={showElementHelp}
+ addItemHandler={createAndAddItemToPath.bind(null, container, property, path)}
+ showOpened={showOpened} onChangeOpenState={setPropertyOpenState.bind(null, container, path, property, !showOpened)}>
+ {children}
+ </List>
);
}
- function buildRemoveListItem(container, property, valuePath, index) {
- const className = ClassNames(property.name + '-remove actions');
+ function buildSimpleList(property, path, value, uniqueId) {
+ if (value && !Array.isArray(value)) {
+ value = [value];
+ }
+ const children = value && value.map((v, i) => {
+ let itemPath = path.concat([i]);
+ return (
+ <ListItemAsLink key={':' + i} property={property} value={v}
+ removeItemHandler={removeListEntry.bind(null, container, property, itemPath)}
+ selectLinkHandler={selectModel.bind(null, container, v, property)} />
+ )
+ });
+ const tip = getTipForProperty(property);
+ const showOpened = getPanelOpenedCondition(value, path);
+ const changeOpenState = setPropertyOpenState.bind(null, container, path, property, !showOpened);
return (
- <div className={className}>
- <h3>
- <span className={property.type + '-name name'}>{changeCase.title(property.name)}</span>
- <span className="info">{index + 1}</span>
- {buildRemovePropertyAction(container, property, valuePath)}
- </h3>
- </div>
+ <List name={uniqueId} id={uniqueId} key={uniqueId} tip={tip}
+ property={property} value={value} readOnly={readOnly} showHelp={showElementHelp}
+ addItemHandler={createAndAddItemToPath.bind(null, container, property, path)}
+ showOpened={showOpened} onChangeOpenState={changeOpenState}>
+ {children}
+ </List>
);
}
- function buildLeafListItem(container, property, valuePath, value, uniqueId, index) {
- // look at the type to determine how to parse the value
+ function buildContainer(property, path, value, uniqueId) {
+ const children = buildComponentsForProperties(property.properties, path, value);
+ const showOpened = getPanelOpenedCondition(value, path);
+ const changeOpenState = setPropertyOpenState.bind(null, container, path, property, !showOpened);
return (
- <div key={uniqueId}>
- {buildRemoveListItem(container, property, valuePath, index)}
- {buildField(container, property, valuePath, value, uniqueId)}
- </div>
-
+ <ContainerWrapper key={uniqueId} id={uniqueId} property={property} readOnly={readOnly}
+ showHelp={showElementHelp} summary={checkIfValueEmpty(value) ? null : '*'}
+ showOpened={showOpened} onChangeOpenState={changeOpenState}>
+ {children}
+ </ContainerWrapper>
);
}
- function build(container, property, path, value, props = {}) {
+ function buildPropertyComponent(property, path, value) {
const fields = [];
- const isLeaf = Property.isLeaf(property);
const isArray = Property.isArray(property);
const isObject = Property.isObject(property);
- const isLeafList = Property.isLeafList(property);
- const isRequired = Property.isRequired(property);
const title = changeCase.titleCase(property.name);
- const columnCount = property.properties.length || 1;
- const isColumnar = isArray && (Math.round(props.width / columnCount) > 155);
- const classNames = {'-is-required': isRequired, '-is-columnar': isColumnar};
// create a unique Id for use as react component keys and html element ids
// use uid (from ui info) instead of id property (which is not stable)
- let uniqueId = container.uid;
- let containerRef = container;
- while (containerRef.parent) {
- uniqueId = containerRef.parent.uid + ':' + uniqueId;
- containerRef = containerRef.parent;
- }
- uniqueId += ':' + path.join(':')
+ let uniqueId = idMaker(container, path);
if (!property.properties && isObject) {
+ console.debug('no properties', property);
const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
property.properties = uiState.properties;
}
- const hasProperties = _isArray(property.properties) && property.properties.length;
- const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(property);
-
- // ensure value is not undefined for non-leaf property types
- if (isObject) {
- if (typeof value !== 'object') {
- value = isArray ? [] : {};
- }
- }
- const valueAsArray = _isArray(value) ? value : isLeafList && typeof value === 'undefined' ? [] : [value];
-
- const isMetaField = property.name === 'meta';
- const isCVNFD = property.name === 'constituent-vnfd';
- const isSimpleListView = Property.isSimpleList(property);
-
- valueAsArray.forEach((value, index) => {
-
- let field;
- const valuePath = path.slice();
- // create a unique field Id for use as react component keys and html element ids
- // notes:
- // keys only need to be unique on components in the same array
- // html element ids should be unique with the document (or form)
- let fieldId = uniqueId;
-
- if (isArray) {
- valuePath.push(index);
- fieldId = isLeafList ? fieldId + index + value : resolveReactKey(value);
- }
-
- if (isMetaField) {
- if (typeof value === 'object') {
- value = JSON.stringify(value, undefined, 12);
- } else if (typeof value !== 'string') {
- value = '{}';
- }
- }
-
- if (isMissingDescriptorMeta) {
- field = <span key={fieldId} className="warning">No Descriptor Meta for {property.name}</span>;
- } else if (property.type === 'choice') {
- field = buildChoice(container, property, valuePath, value, fieldId);
- } else if (isSimpleListView) {
- field = buildSimpleListItem(container, property, valuePath, value, fieldId, index);
- } else if (isLeafList) {
- field = buildLeafListItem(container, property, valuePath, value, fieldId, index);
- } else if (hasProperties) {
- field = buildElement(container, property, valuePath, value, fieldId)
- } else {
- field = buildField(container, property, valuePath, value, fieldId);
- }
-
- function onClickLeaf(property, path, value, event) {
- if (event.isDefaultPrevented()) {
- return;
- }
- event.preventDefault();
- event.stopPropagation();
- this.getRoot().uiState.focusedPropertyPath = path.join('.');
- console.debug('property selected', path.join('.'));
- ComposerAppActions.propertySelected([path.join('.')]);
- }
-
- const clickHandler = isLeaf ? onClickLeaf : () => {};
- const isContainerList = isArray && !(isSimpleListView || isLeafList);
-
- fields.push(
- <div key={fieldId}
- className={ClassNames('property-content', {'simple-list': isSimpleListView})}
- onClick={clickHandler.bind(container, property, valuePath, value)}>
- {isContainerList ? buildRemoveListItem(container, property, valuePath, index) : null}
- {field}
- </div>
+ if (property.type === 'leaf') {
+ return buildField(property, path, value, uniqueId);
+ } else if (property.type === 'leaf_list') {
+ return buildLeafList(property, path, value, uniqueId);
+ } else if (property.type === 'list') {
+ return Property.isSimpleList(property) ?
+ buildSimpleList(property, path, value, uniqueId)
+ :
+ buildList(property, path, value, uniqueId);
+ } else if (property.type === 'container') {
+ return buildContainer(property, path, value, uniqueId);
+ } else if (property.type === 'choice') {
+ return buildChoice(property, path, value, uniqueId);
+ } else {
+ return (
+ <span key={fieldId} className="warning">No Descriptor Meta for {property.name}</span>
);
-
- });
-
- classNames['-is-leaf'] = isLeaf;
- classNames['-is-array'] = isArray;
- classNames['cols-' + columnCount] = isColumnar;
-
- if (property.type === 'choice') {
- value = utils.resolvePath(container.model, ['uiState.choice'].concat(path, 'selected').join('.'));
- if(!value) {
- property.properties.map(function(p) {
- let pname = p.properties[0] && p.properties[0].name;
- if(container.model.hasOwnProperty(pname)) {
- value = container.model[pname];
- }
- })
- }
}
+ }
- let displayValue = typeof value === 'object' ? '' : value;
- const displayValueInfo = isArray ? valueAsArray.filter(d => typeof d !== 'undefined').length + ' items' : '';
-
- const onFocus = isLeaf ? event => event.target.classList.add('-is-focused') : false;
-
- return (
- <div key={uniqueId} className={ClassNames(property.type + '-property property', classNames)} onFocus={onFocus}>
- <h3 className="property-label">
- <label htmlFor={uniqueId}>
- <span className={property.type + '-name name'}>{title}</span>
- <small>
- <span className={property.type + '-info info'}>{displayValueInfo}</span>
- <span className={property.type + '-value value'}>{displayValue}</span>
- </small>
- {isArray ? buildAddPropertyAction(container, property, path.concat(valueAsArray.length)) : null}
- </label>
- </h3>
- <span className={property.type + '-description description'}>{property.description}</span>
- <val className="property-value">
- {isCVNFD ? <span className={property.type + '-tip tip'}>Drag a VNFD from the Catalog to add more.</span> : null}
- {fields}
- </val>
- </div>
- );
+ if (!(DescriptorModelFactory.isContainer(container))) {
+ return
}
const containerType = container.uiState['qualified-type'] || container.uiState.type;
- const basicProperties = getDescriptorMetaBasicForType(containerType).properties;
-
- function buildBasicGroup() {
- if (basicProperties.length === 0) {
- return null;
- }
- return (
- <div className="basic-properties-group">
- <h2>Basic</h2>
- <div>
- {buildComponentsForProperties(container, basicProperties, [], container.model)}
- </div>
- </div>
- );
+ let properties = DescriptorModelMetaFactory.getModelMetaForType(containerType).properties;
+ const breadcrumb = [];
+ if (container.parent) {
+ breadcrumb.push(container.parent);
}
-
- function buildAdvancedGroup() {
- const properties = getDescriptorMetaAdvancedForType(containerType).properties;
- if (properties.length === 0) {
- return null;
+ breadcrumb.push(container);
+ // bubble all data properties to top of list
+ let twoLists = properties.reduce((o, property) => {
+ const value = _get(container.model, [property.name]);
+ if (isDataProperty(property)) {
+ o.listOne.push(property);
+ } else {
+ o.listTwo.push(property);
}
- const hasBasicFields = basicProperties.length > 0;
- const closeGroup = basicProperties.length > 0;
- return (
- <div className="advanced-properties-group">
- <h1 data-toggle={closeGroup ? 'true' : 'false'} className={ClassNames({'-is-toggled': closeGroup})} onClick={toggle} style={{display: hasBasicFields ? 'block' : 'none'}}>
- <a className="toggle-show-more" href="#show-more-properties">more…</a>
- <a className="toggle-show-less" href="#show-more-properties">less…</a>
- </h1>
- <div className="toggleable">
- {buildComponentsForProperties(container, properties, [], container.model, {toggle: true, width: props.width})}
- </div>
- <div className="toggle-bottom-spacer" style={{visibility: 'hidden', 'height': '50%', position: 'absolute'}}>We need this so when the user closes the panel it won't shift away and scare the bj out of them!</div>
- </div>
- );
- }
-
- function buildMoreLess(d, i) {
- return (
- <span key={'bread-crumb-part-' + i}>
- <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
- <i> / </i>
- </span>
- );
+ return o;
+ }, { listOne: [], listTwo: [] });
+ properties = twoLists.listOne.concat(twoLists.listTwo);
+ const children = buildComponentsForProperties(properties, [], container.model);
+
+ function onClick(event) {
+ console.debug(event.target);
+ if (event.isDefaultPrevented()) {
+ return;
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ // notifyFocusedHandler();
}
- const path = [];
- if (container.parent) {
- path.push(container.parent);
+ function onWrapperFocus(event) {
+ console.debug(event.target);
+ //notifyFocusedHandler();
}
- path.push(container);
return (
- <div className="EditDescriptorModelProperties -is-tree-view">
- <h1>{path.map(buildMoreLess)}</h1>
- {buildBasicGroup()}
- {buildAdvancedGroup()}
+ <div className="EditDescriptorModelProperties -is-tree-view" onClick={onClick} onFocus={onWrapperFocus}>
+ <ModelBreadcrumb path={breadcrumb} />
+ {children}
</div>
);
};
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
import onCutDelegateToRemove from './onCutDelegateToRemove'
import onClickSelectAndShowInDetailsPanel from './onClickSelectAndShowInDetailsPanel'
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
import '../../styles/EditForwardingGraphPaths.scss'
import imgNSD from '../../images/default-catalog-icon.svg'
<div key={i} className={fg.className} data-uid={fg.uid} data-offset-width="true" onClick={onClickSelectAndShowInDetailsPanel.bind(null, fg)} onCut={onCutDelegateToRemove.bind(null, fg)}>
<div key="outline-indicator" data-outline-indicator="true"></div>
<div className="header-actions">
- <Button className="remove-forwarding-graph" title="Remove" onClick={onClickRemoveForwardingGraph.bind(null, fg)} src={imgRemove}/>
+ {
+ this.isRBACValid ?
+ <Button className="remove-forwarding-graph" title="Remove" onClick={onClickRemoveForwardingGraph.bind(null, fg)} src={imgRemove}/>
+ : null
+ }
</div>
<LayoutRow primaryActionColumn={toggleSelectAllPaths} secondaryActionColumn={<img className="fg-icon" src={imgFG} width="20px" />}>
<small>{fg.title}</small>
{fg.classifier.map(mapClassifier.bind(null, context))}
<div className="footer-actions">
<div className="row-action-column">
- <Button className="create-new-classifier" src={imgAdd} width="20px" onClick={onClickAddClassifier.bind(null, context, fg)} label="Add Classifier" />
+ {
+ this.isRBACValid ?
+ <Button className="create-new-classifier" src={imgAdd} width="20px" onClick={onClickAddClassifier.bind(null, context, fg)} label="Add Classifier" />
+ : null
+ }
</div>
</div>
</div>
{forwardingGraphs}
<div className="footer-actions">
<div className="row-action-column">
- <Button className="create-new-forwarding-graph" src={imgAdd} width="20px" onClick={onClickAddForwardingGraph.bind(null, nsd)} label="Add new Forwarding Graph" />
+ {
+ this.isRBACValid ?
+ <Button className="create-new-forwarding-graph" src={imgAdd} width="20px" onClick={onClickAddForwardingGraph.bind(null, nsd)} label="Add new Forwarding Graph" />
+ : null
+ }
</div>
</div>
</div>
},
componentWillUnmount: function () {
},
+ contextTypes: {
+ userProfile: React.PropTypes.object
+ },
render() {
-
const containers = this.props.containers;
const context = {
component: this,
- containers: containers
+ containers: containers,
+ isRBACValid: isRBACValid(this.context.userProfile, [PROJECT_ROLES.PROJECT_ADMIN, PROJECT_ROLES.CATALOG_ADMIN])
};
-
const networkService = containers.filter(d => d.type === 'nsd');
if (networkService.length === 0) {
return <p className="welcome-message">No <img src={imgNSD} width="20px" /> NSD open in the canvas. Try opening an NSD.</p>;
--- /dev/null
+
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react'
+import _keys from 'lodash/keys'
+import _get from 'lodash/get'
+import _isObject from 'lodash/isObject'
+import _isArray from 'lodash/isArray'
+import Button from './Button'
+import PropertyCrumb from './model/PropertyCrumb'
+import PropertyNavigate from './model/PropertyNavigate'
+import DescriptorEditorActions from '../actions/DescriptorEditorActions'
+import Select from 'react-select';
+import 'react-select/dist/react-select.css'
+import '../styles/DetailsPanelErrors.scss'
+
+export default function (props) {
+ const { container, idMaker, style } = props;
+ const errors = container.uiState.error;
+ const errorOptions = _keys(errors).reduce((outer, k) => {
+ function pieceOfPath(obj, key) {
+ const node = obj[key];
+ if (_isArray(node)) {
+ const items = node.reduce((inner, v, i) => {
+ if (v) {
+ const paths = _keys(v).reduce((a, k) => {
+ const paths = pieceOfPath(v, k);
+ return a.concat(paths);
+ }, []);
+ // inner = paths.map(p => [i].concat(p));
+ inner = paths.map(p => [i].concat(p));
+ }
+ return inner;
+ }, []);
+ return items.map(i => [key].concat(i));
+ } else if (_isObject(node)) {
+ return _keys(node).reduce((inner, k) => {
+ const paths = pieceOfPath(node, k);
+ return inner.concat(paths);
+ }, []);
+ } else {
+ return [key];
+ }
+ }
+ const paths = pieceOfPath(errors, k);
+ return outer.concat(paths);
+ }, []).reduce((a, path) => {
+ // only add if there is an error message
+ _get(errors, path) && a.push({ value: path, label: _isArray(path) ? path.join(' . ') : path})
+ return a;
+ }, []);
+ return errorOptions.length ?
+ (<PropertyNavigate container={container} idMaker={idMaker} options={errorOptions}
+ style={style} placeholder="Select error to correct" />)
+ : <div />;
+}
+
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+/**
+ * Created by onvelocity on 1/18/16.
+ *
+ * This class generates the form fields used to edit the CONFD JSON model.
+ */
+
+import _uniqueId from 'lodash/uniqueId';
+import _set from 'lodash/set';
+import _get from 'lodash/get';
+import _has from 'lodash/has';
+import _keys from 'lodash/keys';
+import _isObject from 'lodash/isObject';
+import _isArray from 'lodash/isArray';
+import _isNumber from 'lodash/isNumber';
+import utils from '../libraries/utils'
+import React from 'react'
+import changeCase from 'change-case'
+import toggle from '../libraries/ToggleElementHandler'
+import Property from '../libraries/model/DescriptorModelMetaProperty'
+import SelectionManager from '../libraries/SelectionManager'
+import ComposerAppActions from '../actions/ComposerAppActions'
+import CatalogItemsActions from '../actions/CatalogItemsActions'
+import DescriptorEditorActions from '../actions/DescriptorEditorActions'
+import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
+import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
+import PropertyNavigate from './model/PropertyNavigate'
+
+
+import '../styles/EditDescriptorModelProperties.scss'
+
+function selectModel(container, model) {
+ const root = container.getRoot();
+ if (SelectionManager.select(model)) {
+ CatalogItemsActions.catalogItemMetaDataChanged(root.model);
+ }
+}
+
+function isDataProperty(property) {
+ return property.type === 'leaf' || property.type === 'leaf_list' || property.type === 'choice';
+}
+
+function checkIfValueEmpty(value) {
+ if (value === null || typeof value === 'undefined') {
+ return true;
+ } else if (_isArray(value) && !value.length) {
+ return true;
+ } else if (_isObject(value)) {
+ const keys = _keys(value);
+ if (keys.length < 2) {
+ return !keys.length || (keys[0] === 'uiState')
+ }
+ }
+ return false;
+}
+
+function makeOption(path, value) {
+ let labelPath = path.map(node => _isNumber(node) ? node + 1: node);
+ return {
+ value: path,
+ label: labelPath.join(' . ') + (value ? ' : ' + value : '')
+ }
+}
+
+export default function NavigateDescriptorModel(props) {
+ const { container, idMaker, style } = props;
+ const uiState = container.uiState;
+
+ function buildField(property, path, value) {
+ return [makeOption(path, value)];
+ }
+
+ function buildLeafList(property, path, value) {
+ const searchValue = Array.isArray(value) ? value.join(' ') : value;
+ return [makeOption(path, searchValue)];
+ }
+
+ function buildChoice(property, path, value) {
+ const uiStatePath = path.concat(['uiState']);
+ const choiceStatePath = ['choice', property.name];
+ const fullChoiceStatePath = uiStatePath.concat(choiceStatePath);
+
+ function determineSelectedChoice(model) {
+ let choiceState = utils.resolvePath(container.model, fullChoiceStatePath.join('.'));
+ if (choiceState) {
+ return property.properties.find(c => c.name === choiceState.selected);
+ }
+ const selectedCase = property.properties.find(c =>
+ c.properties && c.properties.find(p => _has(model, path.concat([p.name])))
+ );
+ return selectedCase;
+ }
+
+ const selectedCase = determineSelectedChoice(container.model);
+ return [makeOption(path)].concat(selectedCase ?
+ buildComponentsForProperties(
+ selectedCase.properties, path, path.length ? _get(container.model, path) : container.model) :
+ []);
+ }
+
+ function buildList(property, path, value, uniqueId) {
+ if (value && !Array.isArray(value)) {
+ value = [value];
+ }
+
+ function getListItemSummary(index, value) {
+ const keys = property.key.map((key) => value[key]);
+ const summary = keys.join(' ');
+ return summary.length > 1 ? summary : '' + (index + 1);
+ }
+ const children = value ? value.reduce((a, itemValue, i) => {
+ const itemPath = path.concat([i]);
+ return a.concat(buildComponentsForProperties(property.properties, itemPath, itemValue));
+ }, [makeOption(path)])
+ : [makeOption(path)];
+ return children;
+ }
+
+ function buildSimpleList(property, path, value, uniqueId) {
+ return [makeOption(path)];
+ }
+
+ function buildContainer(property, path, value, uniqueId, readOnly) {
+ return buildComponentsForProperties(property.properties, path, value);
+ }
+
+ /**
+ * buiid and return an array of components representing an editor for each property.
+ *
+ * @param {any} container the master document being edited
+ * @param {[property]} properties
+ * @param {string} pathToProperties path within the container to the properties
+ * @param {Object} data source for each property
+ * which may be useful/necessary to a components rendering.
+ * @returns an array of react components
+ */
+ function buildComponentsForProperties(properties, pathToProperties, data) {
+ return properties.reduce((a, property) => {
+ let value;
+ let propertyPath = pathToProperties.slice();
+ if (property.type != 'choice') {
+ propertyPath.push(property.name);
+ }
+ if (data && typeof data === 'object') {
+ value = _get(data, property.name);
+ }
+ let result = [];
+ try {
+ result = buildPropertyComponent(property, propertyPath, value);
+ } catch (e) {
+ console.error(e);
+ }
+ return a.concat(result);
+ }, []);
+ }
+
+ function buildPropertyComponent(property, path, value) {
+
+ const fields = [];
+ const isObject = Property.isObject(property);
+ const title = changeCase.titleCase(property.name);
+
+ // create a unique Id for use as react component keys and html element ids
+ // use uid (from ui info) instead of id property (which is not stable)
+ let uniqueId = container.uid;
+ let containerRef = container;
+ while (containerRef.parent) {
+ uniqueId = containerRef.parent.uid + ':' + uniqueId;
+ containerRef = containerRef.parent;
+ }
+ uniqueId += ':' + path.join(':')
+
+ if (!property.properties && isObject) {
+ console.debug('no properties', property);
+ const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
+ property.properties = uiState.properties;
+ }
+
+ if (property.type === 'leaf') {
+ return buildField(property, path, value, uniqueId);
+ } else if (property.type === 'leaf_list') {
+ return buildLeafList(property, path, value, uniqueId);
+ } else if (property.type === 'list') {
+ return Property.isSimpleList(property) ?
+ buildSimpleList(property, path, value, uniqueId) :
+ buildList(property, path, value, uniqueId);
+ } else if (property.type === 'container') {
+ return buildContainer(property, path, value, uniqueId);
+ } else if (property.type === 'choice') {
+ return buildChoice(property, path, value, uniqueId);
+ } else {
+ return ([]);
+ }
+ }
+
+
+ if (!(DescriptorModelFactory.isContainer(container))) {
+ return null;
+ }
+
+ const containerType = container.uiState['qualified-type'] || container.uiState.type;
+ let properties = DescriptorModelMetaFactory.getModelMetaForType(containerType).properties;
+ // bubble all data properties to top of list
+ let twoLists = properties.reduce((o, property) => {
+ const value = _get(container.model, [property.name]);
+ if (isDataProperty(property)) {
+ o.listOne.push(property);
+ } else {
+ o.listTwo.push(property);
+ }
+ return o;
+ }, {
+ listOne: [],
+ listTwo: []
+ });
+ properties = twoLists.listOne.concat(twoLists.listTwo);
+ const options = buildComponentsForProperties(properties, [], container.model);
+ return options.length ?
+ (<PropertyNavigate container={container} idMaker={idMaker} options={options}
+ style={style} placeholder="Select to navigate" />)
+ : <div />;
+};
\ No newline at end of file
},
componentDidMount() {
RiftHeaderStore.listen(this.onChange);
- const loadCatalogs = function () {
- RiftHeaderStore.loadCatalogs();
- uiTransientState.timeoutId = setTimeout(loadCatalogs, 2000);
- };
RiftHeaderStore.requestLaunchpadConfig();
- loadCatalogs();
},
componentDidUpdate() {
},
this.setState(state);
},
onClickOpenDashboard() {
- if (uiTransientState.timeoutId) {
- clearTimeout(uiTransientState.timeoutId);
- }
RiftHeaderStore.unlisten(this.onChange);
window.location.href = '//' + window.location.hostname + ':8000/index.html?api_server=' + utils.getSearchParams(window.location).api_server + '#/launchpad/' + utils.getSearchParams(window.location).mgmt_domain_name;
},
{ id: 'ICONS', folder: 'icons', title: "Icons", allowFolders: false },
{ id: 'SCRIPTS', folder: 'scripts', title: "scripts", allowFolders: true },
{ id: 'NS_CONFIG', folder: 'ns_config', title: "NS Config", allowFolders: false },
- { id: 'VNF_CONFIG', folder: 'vnf_config', title: "VNF Config", allowFolders: false }
+ { id: 'VNF_CONFIG', folder: 'vnf_config', title: "VNF Config", allowFolders: false },
+ { id: 'DOC', folder: 'doc', title: "Doc", allowFolders: false },
+ { id: 'TEST', folder: 'test', title: "Test", allowFolders: false }
],
'vnfd': [
{ id: 'ICONS', folder: 'icons', title: "Icons", allowFolders: false },
{ id: 'CHARMS', folder: 'charms', title: "charms", allowFolders: true },
{ id: 'SCRIPTS', folder: 'scripts', title: "scripts", allowFolders: true },
- { id: 'IMAGES', folder: 'images', title: "images", allowFolders: false },
{ id: 'CLOUD_INIT', folder: 'cloud_init', title: "cloud_init", allowFolders: false },
+ { id: 'DOC', folder: 'doc', title: "Doc", allowFolders: false },
+ { id: 'TEST', folder: 'test', title: "Test", allowFolders: false },
{ id: 'README', folder: '.', title: "readme", allowFolders: false }
]
}
folders.reverse();
assets[assetGroup.id] = folders.map(fullName => {
let path = fullName.slice(typeFolder.length + 1);
- let files = assetInfo.data[fullName].map(info => ({
- name: info.name.startsWith(fullName) ? info.name.slice(fullName.length + 1) : info.name,
- status: filesStatus[info.name]
- }));
+ let files = assetInfo.data[fullName].reduce((assets, info) =>
+ {
+ let name = info.name.startsWith(fullName) ? info.name.slice(fullName.length + 1) : info.name;
+ let status = filesStatus[info.name];
+ if (fullName !== '.' || !(name.endsWith('.yaml') || name.startsWith('checksum'))) {
+ assets.push({ name, status });
+ }
+ return assets;
+ }
+ , []);
return { path, files };
});
}
}
render() {
let { files, filesState, type, item, actions } = this.props;
+ if (!item) {
+ return null;
+ }
let assets = normalizeAssets(type, files, filesState);
let children = [];
+ const User = this.props.User || {};
+ const ProjectID = User.projectId;
let assetTypes = ASSET_TYPE[type];
assetTypes.forEach(assetGroup => {
const typeFolder = assetGroup.folder;
allowsFolders={assetGroup.allowFolders}
folders={subFolders}
showNotification={actions.showNotification}
+ ProjectID={ProjectID}
/>
)
}, this);
<Panel title={title} itemClassName="nested" no-corners>
{folderCreateComponent}
<div className="folder">
- <FileAssetList files={files} path={path} packageId={packageId} packageType={packageType} assetGroup={assetGroup} />
+ <FileAssetList files={files} path={path} packageId={packageId} packageType={packageType} assetGroup={assetGroup} ProjectID={this.props.ProjectID} />
<Panel className="addFileSection" no-corners>
<ItemUpload packageType={packageType} packageId={packageId} path={path} assetGroup={assetGroup} />
<div style={{ marginLeft: '0.5rem' }}>
if (files) {
children = files.map(function (file, i) {
if (!file.hasOwnProperty('contents')) {
- return <FileAsset key={file.name} file={file} path={path} id={packageId} type={packageType} assetGroup={assetGroup} />
+ return <FileAsset ProjectID={props.ProjectID} key={file.name} file={file} path={path} id={packageId} type={packageType} assetGroup={assetGroup} />
}
})
}
}
function FileAsset(props) {
- let { file, path, type, assetGroup, id } = props;
+ let { file, path, type, assetGroup, id, ProjectID } = props;
const name = file.name;
const downloadHost = API_SERVER.match('localhost') || API_SERVER.match('127.0.0.1') ? `${window.location.protocol}//${window.location.hostname}` : API_SERVER;
//{`${window.location.protocol}//${API_SERVER}:4567/api/package${type}/${id}/${path}/${name}`}
{file.status && (file.status == 'IN_PROGRESS' || file.status == 'DOWNLOADING') ? <LoadingIndicator size={2} /> : file.status}
</div>
<div className="file-name">
- <a target="_blank" href={`${downloadHost}:4567/api/package/${type}/${id}/${assetGroup.folder}${path}/${name}`}>{name}</a>
+ <a target="_blank" href={`${downloadHost}:8008/mano/api/package/${type}/${ProjectID}/${id}/${assetGroup.folder}${path}/${name}`}>{name}</a>
</div>
</div>
<div className="file-action"
openDownloadMonitoringSocket: function() {
return {
remote: function(state, packageID) {
+ let encodedId = encodeURIComponent(packageID);
return new Promise(function(resolve, reject) {
//api/operational/download-jobs/job/
$.ajax({
- url: '/socket-polling?api_server=' + API_SERVER,
+ url: '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
- url: 'composer/api/file-manager/jobs/' + packageID + '?api_server=' + API_SERVER,
+ url: 'composer/api/file-manager/jobs/' + encodedId + '?api_server=' + API_SERVER,
},
success: function(data, textStatus, jqXHR) {
Utils.checkAndResolveSocketRequest(data, resolve, reject);
openFileMonitoringSocket: function() {
return {
remote: function(state, id, type) {
+ let encodedId = encodeURIComponent(id);
return new Promise(function(resolve, reject) {
//api/operational/download-jobs/job/
$.ajax({
- url: '/socket-polling?api_server=' + API_SERVER,
+ url: '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
- url: 'composer/api/file-manager?api_server=' + utils.getSearchParams(window.location).api_server +'&package_type=' + type + '&package_id=' + id
+ url: 'composer/api/file-manager?api_server=' + utils.getSearchParams(window.location).api_server +'&package_type=' + type + '&package_id=' + encodedId
},
success: function(data, textStatus, jqXHR) {
Utils.checkAndResolveSocketRequest(data, resolve, reject);
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
detailsWelcome() {
return <p className="welcome-message">Select an object to view details.</p>;
},
- canvasWelcome() {
+ canvasWelcome(isValid) {
return (
<span>
<p className="welcome-message">Double-click a Descriptor to open.</p>
- <p className="welcome-message">Or drag a Descriptor to add to Canvas.</p>
+ {isValid ? <p className="welcome-message">Or drag a Descriptor to add to Canvas.</p> : null}
</span>
);
},
get showLessTitle() {
return 'Show Less';
},
- get catalogWelcome() {
- return <p className="welcome-message">To onboard a descriptor, drag the package to the catalog or click the Onboard button (<img style={{width: '20px'}} src={imgOnboard} />) to select the package.</p>;
+ catalogWelcome(isValid) {
+ return isValid ? <p className="welcome-message">To onboard a descriptor, drag the package to the catalog or click the Onboard button (<img style={{width: '20px'}} src={imgOnboard} />) to select the package.</p> : <p className="welcome-message"> No descriptors have been onboarded</p> ;
},
getSaveActionLabel(isNew) {
return isNew ? 'Onboard' : 'Update';
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import React from 'react';
+import changeCase from 'change-case'
+import Button from '../Button'
+import ClassNames from 'classnames'
+import CatalogItemsActions from '../../actions/CatalogItemsActions'
+import DescriptorModelMetaFactory from '../../libraries/model/DescriptorModelMetaFactory'
+import Property from '../../libraries/model/DescriptorModelMetaProperty'
+import utils from '../../libraries/utils'
+
+import PropertyPanel from './PropertyPanel'
+import Select from './Select'
+
+import '../../styles/EditDescriptorModelProperties.scss'
+import imgAdd from '../../../../node_modules/open-iconic/svg/plus.svg'
+
+export default function (props) {
+ const { id, property, value, readOnly, onChange, showHelp, children } = props;
+
+ const title = changeCase.titleCase(property.name);
+ const helpText = showHelp ? property.description : null;
+ const cases = property.properties.map(d => ({ name: d.name, value: d.name }));
+
+ return (
+ <div className='-is-leaf'>
+ <PropertyPanel title={title} helpText={helpText}>
+ <div className="choice">
+ <Select
+ id={id}
+ value={value}
+ options={cases}
+ title={title}
+ placeholder={property.name}
+ onChange={onChange}
+ readOnly={readOnly} />
+ {children}
+ </div>
+ </PropertyPanel >
+ </div >
+ );
+}
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import React from 'react'
+import PropertyPanel from './PropertyPanel'
+import changeCase from 'change-case'
+
+import '../../styles/EditDescriptorModelProperties.scss'
+
+export default function (props) {
+ const { id, property, summary, showHelp, showOpened, onChangeOpenState, children } = props;
+ const title = changeCase.titleCase(property.name);
+ const helpText = showHelp ? property.description : null;
+ const info = showOpened ? null : summary;
+ return (
+ <div id={id} className='-is-container'>
+ <PropertyPanel title={title} info={info} helpText={helpText}
+ showOpened={showOpened} onChangeOpenState={onChangeOpenState}>
+ <div className={'property-content'} >
+ {children}
+ </div>
+ </PropertyPanel>
+ </div>
+ );
+}
--- /dev/null
+/*
+ *
+ * Copyright 2016-2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import getEventPath from '../../libraries/getEventPath'
+import DeletionManager from '../../libraries/DeletionManager'
+
+function startEditing() {
+ DeletionManager.removeEventListeners();
+}
+
+function endEditing() {
+ DeletionManager.addEventListeners();
+}
+
+function onFocusPropertyFormInputElement(event) {
+
+ console.debug('property focus ', event.target.id);
+ event.preventDefault();
+ startEditing();
+
+ function removeIsFocusedClass(event) {
+ event.target.removeEventListener('blur', removeIsFocusedClass);
+ Array.from(document.querySelectorAll('.-is-focused')).forEach(d => d.classList.remove('-is-focused'));
+ }
+
+ removeIsFocusedClass(event);
+
+ const propertyWrapper = getEventPath(event).reduce((parent, element) => {
+ if (parent) {
+ return parent;
+ }
+ if (!element.classList) {
+ return false;
+ }
+ if (element.classList.contains('property')) {
+ return element;
+ }
+ }, false);
+
+ if (propertyWrapper) {
+ propertyWrapper.classList.add('-is-focused');
+ event.target.addEventListener('blur', removeIsFocusedClass);
+ }
+}
+
+function getTitle(model = {}) {
+ if (typeof model['short-name'] === 'string' && model['short-name']) {
+ return model['short-name'];
+ }
+ if (typeof model.name === 'string' && model.name) {
+ return model.name;
+ }
+ if (model.uiState && typeof model.uiState.displayName === 'string' && model.uiState.displayName) {
+ return model.uiState.displayName
+ }
+ if (typeof model.id === 'string') {
+ return model.id;
+ }
+}
+
+export {
+ startEditing,
+ endEditing,
+ onFocusPropertyFormInputElement,
+ getTitle
+}
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2016-2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import _debounce from 'lodash/debounce';
+import React from 'react'
+import ClassNames from 'classnames'
+import changeCase from 'change-case'
+import Property from '../../libraries/model/DescriptorModelMetaProperty'
+import CatalogDataStore from '../../stores/CatalogDataStore'
+import _isInt from 'validator/lib/isInt'
+import _toInt from 'validator/lib/toInt'
+import _isFloat from 'validator/lib/isFloat'
+import _toFloat from 'validator/lib/toFloat'
+import _trim from 'validator/lib/trim'
+import _isIP from 'validator/lib/isIP'
+
+import { startEditing, endEditing, onFocusPropertyFormInputElement } from './EditDescriptorUtils'
+import Select from './Select'
+
+function validateRequired(isRequired, value) {
+ value = value.trim();
+ return isRequired && !value ? { success: false, message: "A value is required." } : { success: true, value: null };
+}
+
+function editorExitHandler(isValueRequired, onExit, onError, event) {
+ const value = event.target.value;
+ const result = validateRequired(isValueRequired, value);
+ onExit && onExit(result);
+ endEditing();
+}
+
+function Enumeration(props) {
+ const { id, property, title, readOnly, onChange, onError, onExit } = props;
+ let value = props.value;
+ const enumeration = Property.getEnumeration(property, value);
+ const hasDefaultValue = !!property['default-value'];
+ const required = property.mandatory || hasDefaultValue;
+ if (!value && hasDefaultValue) {
+ value = property['default-value'];
+ }
+ return (
+ <Select
+ id={id}
+ value={value}
+ options={enumeration}
+ title={title}
+ placeholder={property.name}
+ onChange={onChange}
+ onExit={editorExitHandler.bind(null, required, onExit, onError)}
+ required={required} r
+ readOnly={readOnly} />
+ );
+}
+
+function Reference(props) {
+ const { id, property, title, path, container, readOnly, onChange, onError, onExit } = props;
+ let value = props.value;
+ const catalogs = props.catalogs || CatalogDataStore.getTransientCatalogs();
+ let fullPathString = container.key + ':' + path.join(':');
+ let containerRef = container;
+ while (containerRef.parent) {
+ fullPathString = containerRef.parent.key + ':' + fullPathString;
+ containerRef = containerRef.parent;
+ }
+ const leafRefPathValues = Property.getLeafRef(property, path, value, fullPathString, catalogs, container);
+ const required = property.mandatory;
+ if (value && !leafRefPathValues.find(option => option.isSelected)) {
+ value = null;
+ }
+
+ return (
+ <Select
+ id={id}
+ value={value}
+ options={leafRefPathValues}
+ title={title}
+ placeholder={property.name}
+ onChange={onChange}
+ onExit={editorExitHandler.bind(null, required, onExit, onError)}
+ required={required}
+ readOnly={readOnly} />
+ );
+}
+
+function Boolean(props) {
+ const { id, property, title, readOnly, onChange, onError, onExit } = props;
+ let value = props.value;
+ const hasDefaultValue = !!property['default-value'];
+ const required = property.mandatory || hasDefaultValue;
+ const typeOfValue = typeof value;
+ if (typeOfValue === 'number' || typeOfValue === 'boolean') {
+ value = value ? 'TRUE' : 'FALSE';
+ } else if (value) {
+ value = value.toUpperCase();
+ } else {
+ if (hasDefaultValue) {
+ value = ('' + property['default-value']).toUpperCase();
+ }
+ }
+ const options = [
+ { name: "TRUE", value: 'TRUE' },
+ { name: "FALSE", value: 'FALSE' }
+ ]
+ return (
+ <Select
+ id={id}
+ value={value}
+ options={options}
+ title={title}
+ placeholder={property.name}
+ onChange={onChange}
+ onExit={editorExitHandler.bind(null, required, onExit, onError)}
+ required={required}
+ readOnly={readOnly} />
+ );
+}
+
+function Empty(props) {
+ // A null value indicates the leaf exists (as opposed to undefined).
+ // We stick in a string when the user actually sets it to simplify things
+ // but the correct thing happens when we serialize to user data
+ const EMPTY_LEAF_PRESENT = '--empty-leaf-set--';
+ const { id, property, value, title, readOnly, onChange } = props;
+ let isEmptyLeafPresent = !!value;
+ let present = isEmptyLeafPresent ? EMPTY_LEAF_PRESENT : "";
+ const options = [
+ { name: "Enabled", value: EMPTY_LEAF_PRESENT }
+ ]
+
+ return (
+ <Select
+ id={id}
+ value={present}
+ placeholder={"Not Enabled"}
+ options={options}
+ title={title}
+ onChange={onChange}
+ readOnly={readOnly} />
+ );
+}
+
+function getValidator(property) {
+ function validateInteger(constraints, value) {
+ return _isInt(value, constraints) ? { success: true, value: _toInt(value) } :
+ { success: false, value, message: "The value is not an integer or does not meet the property constraints." };
+ }
+ function validateDecimal(constraints, value) {
+ return _isFloat(value, constraints) ? { success: true, value: _toFloat(value) } :
+ { success: false, value, message: "The value is not a decimal number or does not meet the property constraints." };
+ }
+ function validateProperty(validator, errorMessage, value) {
+ return validator(value) ? { success: true, value } :
+ { success: false, value, message: errorMessage };
+ }
+ const name = property.name;
+ if (name === 'ip-address' || name.endsWith('-ip-address')) {
+ return validateProperty.bind(null, _isIP, "The value is not a valid ip address.")
+ }
+ switch (property['data-type']) {
+ case 'int8':
+ return validateInteger.bind(null, { min: -128, max: 127 });
+ case 'int16':
+ return validateInteger.bind(null, { min: -32768, max: 32767 });
+ case 'int32':
+ return validateInteger.bind(null, { min: -2147483648, max: 2147483647 });
+ case 'int64':
+ return validateInteger.bind(null, null);
+ case 'uint8':
+ return validateInteger.bind(null, { min: 0, max: 255 });
+ case 'uint16':
+ return validateInteger.bind(null, { min: 0, max: 65535 });
+ case 'uint32':
+ return validateInteger.bind(null, { min: 0, max: 4294967295 });
+ case 'uint64':
+ return validateInteger.bind(null, { min: 0 });
+ case 'decimal64':
+ return validateDecimal.bind(null, null)
+ case 'string':
+ default:
+ return function (value) { return { success: true, value } };
+ }
+}
+
+function messageTemplate(strings, ...keys) {
+ return (function (...vars) {
+ let helpInfo = vars.reduce((o, info) => Object.assign(o, info), {});
+ return keys.reduce((s, key, i) => {
+ return s + helpInfo[key] + strings[i + 1];
+ }, strings[0]);
+ });
+}
+
+const errorMessage = messageTemplate`"${'value'}" is ${'error'}. ${'message'}`;
+
+class Input extends React.Component {
+ constructor(props) {
+ super(props);
+ let originalValue = props.value ? props.value : null; // normalize empty value
+ this.state = { originalValue };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const { value } = nextProps
+ if (value !== this.state.originalValue) {
+ let originalValue = value ? value : null; // normalize empty value
+ this.setState({ originalValue })
+ }
+ }
+
+ render() {
+ const { id, property, value, title, readOnly, onChange, onError, onExit } = this.props;
+ const { originalValue } = this.state;
+ const isGuid = Property.isGuid(property);
+ const className = ClassNames(property.name + '-input', { '-is-guid': isGuid, '-is-required': required });
+ const placeholder = property.name;
+ const required = property.mandatory;
+
+ const validator = getValidator(property);
+ function handleValueChanged(newValue) {
+ newValue = newValue.trim();
+ const result = !newValue ? validateRequired(required, newValue) : validator(newValue);
+ result.success ? (originalValue !== result.value) && onChange(result.value) : onError(result.message);
+ }
+ const changeHandler = _debounce(handleValueChanged, 2000);
+ function onInputChange(e) {
+ e.preventDefault();
+ changeHandler(_trim(e.target.value));
+ }
+ function onBlur(e) {
+ changeHandler.cancel();
+ const value = _trim(e.target.value);
+ const result = !value ? validateRequired(required, value) : validator(value);
+ if (result.success) {
+ // just in case we missed it by cancelling the debouncer
+ (originalValue !== result.value) && onChange(result.value);
+ }
+ onExit(result);
+ }
+ if (property['preserve-line-breaks']) {
+ return (
+ <textarea
+ cols="5"
+ id={id}
+ defaultValue={value}
+ placeholder={placeholder}
+ className={className}
+ onChange={readOnly ? null : onInputChange}
+ onFocus={onFocusPropertyFormInputElement}
+ onBlur={readOnly ? null : editorExitHandler.bind(null, required, onExit, onError)}
+ onMouseDown={startEditing}
+ onMouseOver={startEditing}
+ onMouseOut={endEditing}
+ onMouseLeave={endEditing}
+ required={required} readOnly={readOnly} />
+ );
+ }
+ return (
+ <input
+ id={id}
+ type="text"
+ defaultValue={value}
+ className={className}
+ placeholder={placeholder}
+ onChange={readOnly ? null : onInputChange}
+ onFocus={onFocusPropertyFormInputElement}
+ onBlur={readOnly ? null : onBlur}
+ onMouseDown={startEditing}
+ onMouseOver={startEditing}
+ onMouseOut={endEditing}
+ onMouseLeave={endEditing}
+ required={required} readOnly={readOnly}
+ />
+ );
+ }
+}
+
+export {
+ Input,
+ Empty,
+ Boolean,
+ Reference,
+ Enumeration
+};
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import React from 'react';
+import Button from '../Button'
+import ClassNames from 'classnames'
+import changeCase from 'change-case'
+import Property from '../../libraries/model/DescriptorModelMetaProperty'
+import { Input, Empty, Boolean, Reference, Enumeration } from './LeafEditor'
+
+import '../../styles/EditDescriptorModelProperties.scss'
+
+function buildEditor(container, property, path, value, readOnly, id, onChange, onError, onExit) {
+ const title = path.join('.');
+
+ let editor = null;
+ if (Property.isEnumeration(property)) {
+ editor = <Enumeration
+ property={property}
+ id={id}
+ value={value}
+ title={title}
+ onChange={onChange} onError={onError} onExit={onExit}
+ readOnly={readOnly}
+ />
+ } else if (Property.isLeafRef(property)) {
+ editor = <Reference
+ container={container}
+ property={property}
+ path={path}
+ value={value}
+ id={id}
+ title={title}
+ onChange={onChange} onError={onError} onExit={onExit}
+ readOnly={readOnly}
+ />
+ } else if (Property.isBoolean(property)) {
+ editor = <Boolean
+ property={property}
+ id={id}
+ value={value}
+ title={title}
+ onChange={onChange} onError={onError} onExit={onExit}
+ readOnly={readOnly}
+ />
+ } else if (Property.isLeafEmpty(property)) {
+ editor = <Empty
+ property={property}
+ id={id}
+ value={value}
+ title={title}
+ onChange={onChange} onError={onError} onExit={onExit}
+ readOnly={readOnly}
+ />
+ } else if (Property.name === 'meta') {
+ if (typeof value === 'object') {
+ value = JSON.stringify(value, undefined, 12);
+ } else if (typeof value !== 'string') {
+ value = '{}';
+ }
+ } else {
+ editor = <Input
+ property={property}
+ id={id}
+ value={value}
+ title={title}
+ onChange={onChange} onError={onError} onExit={onExit}
+ readOnly={readOnly}
+ />
+ }
+ return editor;
+}
+
+export default class LeafField extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { showHelp: props.showHelp, isInError: props.errorMessage };
+ }
+ componentWillReceiveProps(nextProps) {
+ const { showHelp, errorMessage } = nextProps
+ if (showHelp !== this.state.showHelp && !this.state.isInError) {
+ this.setState({ showHelp })
+ }
+ if (errorMessage !== this.state.errorMessage) {
+ this.setState({ showHelp: !!errorMessage || showHelp, isInError: errorMessage })
+ }
+ }
+ render() {
+ const { container, property, path, value, id, readOnly, onChange, onError } = this.props;
+ let title = changeCase.titleCase(property.name);
+ const showHelp = this.state.showHelp;
+ const description = property.description;
+ const helpText = this.state.isInError ? this.state.isInError + " " + description : description;
+ if (property.mandatory) {
+ title = "* " + title;
+ }
+ const errorHandler = (message) => {
+ this.setState({ showHelp: true, isInError: message });
+ }
+ const changeHandler = (value) => {
+ this.setState({ showHelp: this.props.showHelp, isInError: false });
+ onChange && onChange(value);
+ }
+ const exitHandler = (exitResult) => {
+ if (!exitResult.success) {
+ // errorHandler(exitResult.message);
+ onError && onError(exitResult.message);
+ }
+ }
+
+ const editor = buildEditor(
+ container, property, path, value, readOnly, id,
+ changeHandler, errorHandler, exitHandler);
+ const helpStyle = {
+ display: showHelp ? 'inline-block' : 'none',
+ paddingTop: '2px',
+ color: this.state.isInError ? 'red' : 'inherit'
+ };
+ return (
+ <div className={ClassNames('leaf-property -is-leaf property')}>
+ <h3 className="property-label">
+ <label htmlFor={id}>
+ <span className={'leaf-name name info'}>{title}</span>
+ </label>
+ </h3>
+ <val className="property-value">
+ <div className={ClassNames('property-content')}>
+ {editor}
+ </div>
+ </val>
+ <span className={'leaf-description description'}
+ style={helpStyle}>{helpText}</span>
+ </div>
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import React from 'react';
+import changeCase from 'change-case'
+import Button from '../Button'
+import ClassNames from 'classnames'
+import CatalogItemsActions from '../../actions/CatalogItemsActions'
+import DescriptorModelMetaFactory from '../../libraries/model/DescriptorModelMetaFactory'
+import Property from '../../libraries/model/DescriptorModelMetaProperty'
+import utils from '../../libraries/utils'
+
+import PropertyPanel from './PropertyPanel'
+import PanelHeader from './PanelHeader'
+import RemoveItemButton from './RemoveItemButton'
+
+import '../../styles/EditDescriptorModelProperties.scss'
+import imgAdd from '../../../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../../../node_modules/open-iconic/svg/trash.svg'
+
+function ListItemHeader(props) {
+ const {
+ property, showOpened, onChangeOpenState,
+ info, summary, readOnly, removeItemHandler } = props;
+ const className = ClassNames(property.name + '-property');
+ const title = changeCase.title(property.name);
+ const name = showOpened ? title : summary ? '' : title;
+ const newInfo = showOpened ? info : summary || info;
+ return (
+ <div className={className}>
+ <PanelHeader name={name} info={newInfo}
+ showOpened={showOpened} onChangeOpenState={onChangeOpenState}
+ action={readOnly ? null : { invoke: removeItemHandler, icon: imgRemove, title: "Remove" }} />
+ </div>
+ );
+}
+function ListItem(props) {
+ const {
+ property, info, summary, readOnly,
+ showOpened, onChangeOpenState, children, removeItemHandler } = props;
+ return (
+ <div className={'property-content'}>
+ <ListItemHeader property={property} info={info} summary={summary}
+ showOpened={showOpened} onChangeOpenState={onChangeOpenState}
+ readOnly={readOnly} removeItemHandler={removeItemHandler} />
+ {showOpened ? children : null}
+ </div>
+ );
+}
+
+
+class List extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+ render() {
+ const {
+ property, value, readOnly, showHelp, tip, showOpened,
+ onChangeOpenState, children, addItemHandler, id } = this.props;
+ const title = changeCase.titleCase(property.name);
+ const info = (children ? children.length : '0') + ' items';
+ const description = property.description;
+ const nodeType = property.type;
+ const tipText = tip && !readOnly ?
+ <span key="tip" className={nodeType + '-tip tip'}>{tip}</span>
+ : null;
+
+ let classNames = ['-is-array'];
+ if (property.type === 'leaf_list') {
+ classNames.push('-is-leaf');
+ }
+
+ return (
+ <div id={id} className={ClassNames(classNames)}>
+ <PropertyPanel title={title} info={info} helpText={showHelp ? description : null}
+ showOpened={showOpened} onChangeOpenState={onChangeOpenState}
+ action={readOnly ? null : { invoke: addItemHandler, icon: imgAdd, title: "Add" }}>
+ {tipText ? [tipText].concat(children) : children}
+ </PropertyPanel>
+ </div >
+ );
+ }
+}
+
+List.defaultProps = {
+}
+
+export {
+ List,
+ ListItem
+}
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2016-2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react'
+import Property from '../../libraries/model/DescriptorModelMetaProperty'
+import { getTitle } from './EditDescriptorUtils'
+import DescriptorModelIconFactory from '../../libraries/model/IconFactory'
+import RemoveItemButton from './RemoveItemButton'
+
+export default function ListItemAsLink(props) {
+ const { property, value, removeItemHandler, selectLinkHandler } = props;
+ // todo need to abstract this better
+ const title = getTitle(value);
+ var req = require.context("../../", true, /\.svg/);
+
+ function onClickSelectItem(event) {
+ event.preventDefault();
+ selectLinkHandler();
+ }
+
+ return (
+ <div className='property-content simple-list'>
+ <a href="#select-list-item" className={property.name + '-list-item simple-list-item '} onClick={onClickSelectItem}>
+ <img src={req('./' + DescriptorModelIconFactory.getUrlForType(property.name))} width="20px" />
+ <span>{title}</span>
+ </a>
+ <RemoveItemButton removeItemHandler={removeItemHandler} />
+ </div>
+ );
+}
--- /dev/null
+/*
+ *
+ * Copyright 2016-2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react'
+import ComposerAppActions from '../../actions/ComposerAppActions'
+
+import '../../styles/EditDescriptorModelProperties.scss'
+
+export default function ModelBreadcrumb(props) {
+ const { path } = props;
+ const linkCount = path.length - 1;
+ function onClickSelectItem(node, event) {
+ event.preventDefault();
+ ComposerAppActions.selectModel(node);
+ }
+ const crumbs = path.map((node, i) =>
+ i < linkCount ?
+ <span key={node.title}>
+ <a href="#select-item" onClick={onClickSelectItem.bind(null, node)}>{node.title}</a>
+ <i> / </i>
+ </span>
+ :
+ <span key={node.title}>
+ {node.title}
+ </span>
+ );
+ return <h1>{crumbs}</h1>
+}
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import React from 'react';
+import changeCase from 'change-case'
+import Button from '../Button'
+import { CaretRightIcon } from 'react-open-iconic-svg';
+import { CaretBottomIcon } from 'react-open-iconic-svg';
+
+import '../../styles/EditDescriptorModelProperties.scss'
+
+export default function PanelHeader(props) {
+ const {
+ name, info, action, helpText,
+ showOpened, onChangeOpenState } = props;
+
+ function onClickOpenClose(e) {
+ onChangeOpenState();
+ }
+ function onClickAction(e) {
+ e.preventDefault();
+ action.invoke();
+ }
+
+ const isExpandCollapseButtonNeeded = !!onChangeOpenState;
+ const isOpened = isExpandCollapseButtonNeeded ? showOpened : true;
+ const actionButton = isOpened && action ?
+ <Button className="inline-hint" onClick={onClickAction} label={action.title} src={action.icon} />
+ : null;
+ const expandoButtonStyle = { fill: '#586e75', cursor: 'pointer' };
+ const expandoButton = !isExpandCollapseButtonNeeded ? null : isOpened ?
+ <CaretBottomIcon style={expandoButtonStyle} onClick={onClickOpenClose} />
+ : <CaretRightIcon style={expandoButtonStyle} onClick={onClickOpenClose} />;
+ const help = isOpened && helpText ? <span className={'description'} >{helpText}</span> : null;
+ return (
+ <div>
+ <h3 className='property-label'>
+ {expandoButton}
+ <span className={'name'} onClick={onClickOpenClose} style={{cursor: 'pointer'}} >{name}</span>
+ <small>
+ <span className={'info'}>{info}</span>
+ </small>
+ {actionButton}
+ </h3>
+ <div>{help}</div>
+ </div>
+ )
+}
+
--- /dev/null
+/*
+ *
+ * Copyright 2016-2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react'
+import _keys from 'lodash/keys'
+import _isObject from 'lodash/isObject'
+import DescriptorEditorActions from '../../actions/DescriptorEditorActions'
+import SelectionManager from '../../libraries/SelectionManager'
+
+import '../../styles/EditDescriptorModelProperties.scss'
+
+function makeId(container, path) {
+ let idParts = [path];
+ idParts.push(container.uid);
+ while (container.parent) {
+ container = container.parent;
+ idParts.push(container.uid);
+ }
+ return idParts.reverse().join(':');
+}
+
+export default function PropertyCrumb(props) {
+ const { container, errors } = props;
+ const errorPaths = _keys(errors).reduce((a, k) => {
+ function pieceOfPath(obj, key) {
+ if (_isObject(obj[key])) {
+ const node = obj[key];
+ return _keys(node).reduce((a,k) => {
+ const paths = pieceOfPath(node, k).map(e => key + '.' + e)
+ return a.concat(paths);
+ }, []);
+ } else {
+ return obj[key] ? [key] : [];
+ }
+ }
+ const paths = pieceOfPath(errors, k);
+ return a.concat(paths);
+ }, []);
+
+ function onClickSelectItem(path, event) {
+ event.preventDefault();
+ // DescriptorEditorActions.setFocus({descriptor: container, path})
+ const element = document.getElementById(makeId(container, path));
+ element && element.scrollIntoView() && setTimeout(() => element.focus(), 1);
+
+ // const root = node.getRoot();
+ // if (SelectionManager.select(node)) {
+ // DescriptorEditorActions.catalogItemMetaDataChanged(root.model);
+ // }
+ }
+ const crumbs = errorPaths.map((path, i) =>
+ <span key={path}>
+ <a href="#select-item" onClick={onClickSelectItem.bind(null, path)}>{path}</a>
+ </span>
+ );
+ return (
+ <div style={{ margin: '3px 6px' }} >
+ <h3>{crumbs}</h3>
+ </div>
+ );
+}
--- /dev/null
+/*
+ *
+ * Copyright 2016-2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react'
+import _keys from 'lodash/keys'
+import _isObject from 'lodash/isObject'
+
+import Select from 'react-select';
+import 'react-select/dist/react-select.css'
+
+import DescriptorEditorActions from '../../actions/DescriptorEditorActions'
+import CatalogItemsActions from '../../actions/CatalogItemsActions'
+import Property from '../../libraries/model/DescriptorModelMetaProperty'
+import SelectionManager from '../../libraries/SelectionManager'
+
+export default function PropertyNavigate(props) {
+ const { container, idMaker, options, placeholder, style } = props;
+
+ function gotoProperty(descriptor, path) {
+ DescriptorEditorActions.expandPanel({ descriptor, path });
+ function bringIntoViewAndFocusOnProperty() {
+ const element = document.getElementById(idMaker(container, path));
+ if (element) {
+ element.scrollIntoView();
+ setTimeout(function () {
+ element.focus()
+ }, 100);
+ }
+ }
+ setTimeout(bringIntoViewAndFocusOnProperty, 100);
+ }
+ function onClickSelectItem(item) {
+ // we don't support traversing into an 'external' model (e.g. vlds)
+ // if we did then we would need to know when and then invoke something like
+ // CatalogItemsActions.catalogItemMetaDataChanged(root.model);
+ // and then after a delay fire the gotoProperty step
+ gotoProperty(container, item.value);
+ }
+ return (
+ <div style={style} >
+ <Select
+ placeholder={placeholder}
+ options={options}
+ onChange={onClickSelectItem}
+ scrollMenuIntoView={true}
+ />
+ </div>
+ );
+}
--- /dev/null
+/*
+ *
+ * Copyright 2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import React from 'react';
+import changeCase from 'change-case'
+import Button from '../Button'
+import { TextIcon } from 'react-open-iconic-svg';
+import { CaretRightIcon } from 'react-open-iconic-svg';
+import { CaretBottomIcon } from 'react-open-iconic-svg';
+import PanelHeader from './PanelHeader'
+
+
+import '../../styles/EditDescriptorModelProperties.scss'
+
+export default function PropertyPanel(props) {
+ const {
+ title, children, info, action, helpText,
+ readOnly, showOpened, onChangeOpenState } = props;
+ const isExpandCollapseButtonNeeded = !!onChangeOpenState;
+ const isOpened = isExpandCollapseButtonNeeded ? showOpened : true;
+
+ const val = (isOpened && children && (Array.isArray(children) ? children.length : true)) ?
+ <val className="property-value">
+ {children}
+ </val>
+ : null;
+ return (
+ <div className={'property'}>
+ <PanelHeader name={title} info={info} helpText={helpText}
+ showOpened={showOpened} onChangeOpenState={onChangeOpenState}
+ action={readOnly ? null : action} />
+ {val}
+ </div>
+ );
+}
--- /dev/null
+/*
+ *
+ * Copyright 2016-2017 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react'
+import Property from '../../libraries/model/DescriptorModelMetaProperty'
+import utils from '../../libraries/utils'
+import Button from '../Button'
+import CatalogItemsActions from '../../actions/CatalogItemsActions'
+
+import imgRemove from '../../../../node_modules/open-iconic/svg/trash.svg'
+
+export default function RemoveItemButton(props) {
+ const { removeItemHandler } = props;
+ function onClickRemoveProperty(event) {
+ event.preventDefault();
+ removeItemHandler();
+ }
+ return (
+ <Button
+ className="remove-property-action inline-hint"
+ title="Remove"
+ onClick={onClickRemoveProperty}
+ label="Remove"
+ src={imgRemove}
+ />
+ );
+}
--- /dev/null
+/*
+ *
+ * Copyright 2016-2017 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.
+ *
+ */
+/**
+ * Created by onvelocity on 1/18/16.
+ *
+ * This class generates the form fields used to edit the CONFD JSON model.
+ */
+
+import React from 'react'
+import ClassNames from 'classnames'
+import changeCase from 'change-case'
+import Property from '../../libraries/model/DescriptorModelMetaProperty'
+import CatalogDataStore from '../../stores/CatalogDataStore'
+
+import { startEditing, endEditing, onFocusPropertyFormInputElement } from './EditDescriptorUtils'
+import '../../styles/EditDescriptorModelProperties.scss'
+
+export default function Select(props) {
+ const { id, label, value, options, title, required, readOnly, onChange } = props;
+ const selectOptions = options.map((o, i) => <option key={':' + i} value={o.value}>{o.name}</option>);
+ if (!value || !required) {
+ let placeholder = props.placeholder || " ";
+ placeholder = changeCase.title(placeholder);
+ const noValueDisplayText = placeholder;
+ selectOptions.unshift(<option key={'(value-not-set)'} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
+ }
+ function onSelectChange(e) {
+ e.preventDefault();
+ onChange(e.target.value);
+ }
+ return (
+ <select
+ id={id}
+ className={ClassNames({ '-value-not-set': !value, '-is-required': required })}
+ defaultValue={value}
+ title={title}
+ onChange={onSelectChange}
+ onFocus={onFocusPropertyFormInputElement}
+ onBlur={endEditing}
+ onMouseDown={startEditing}
+ onMouseOver={startEditing}
+ required={required}
+ disabled={readOnly}>
+ {selectOptions}
+ </select>
+ );
+}
let dev_download_server = Utils.getSearchParams(window.location).dev_download_server;
DropZone.autoDiscover = false;
return new DropZone(element, {
- paramName: 'package',
+ paramName: 'file',
url() {
let {packageType, packageId, assetGroup, path} = getUploadProps();
if (action === ACTIONS.update) {
+++ /dev/null
-/*
- *
- * Copyright 2016 RIFT.IO Inc
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import guid from '../libraries/guid'
-import DropZone from 'dropzone'
-import Utils from '../libraries/utils'
-import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions'
-import ReactDOM from 'react-dom'
-import $ from 'jquery'
-
-const API_SERVER = Utils.getSearchParams(window.location).api_server;
-
-
-
-
-export default class PackageManager {
- constructor(element, button, action) {
- this.stagingArea = {
- packages: {
- ids: []
- }
- }
- this.stagingAreaMonitor = null;
- }
- createStagingArea(type, name) {
- return $.ajax({
- url: Utils.getSearchParams(window.location).api_server + ':8008/api/operations/create-staging-area',
- type: 'POST',
- data: {
- "input" : {
- // Package type not important for package upload.
- "package-type": type || "NSD",
- "name": name || "Package Staging Area"
- }
- },
- error: function() {
- console.log('Something went wrong creating the staging area: ', arguments)
- }
- }).then(function(data) {
- /*
- {
- "output": {
- "endpoint": "api/upload/85f8e2dc-638b-46e7-89cb-ee8de322066f",
- "port": "4568"
- }
- }
- */
- const id = data.output.endpoint.split('/')[2];
- const port = data.output.port;
- this.stagingArea.packages.ids.push(id);
- this.stagingArea.packages[id] = {
- port: port
- };
- return data
- })
- }
- monitoringStagingAreaSocket() {
- let self = this;
- if(self.stagingAreaMonitor) {
- return self.stagingAreaMonitor;
- }
- new Promise(function(resolve, reject) {
- $.ajax({
- url: '/socket-polling',
- type: 'POST',
- beforeSend: Utils.addAuthorizationStub,
- data: {
- url: 'launchpad/api/nsr?api_server=' + API_SERVER
- },
- success: function(data, textStatus, jqXHR) {
- Utils.checkAndResolveSocketRequest(data, resolve, reject, self.monitoringStagingAreaSocketHandler);
- }
- })
- })
-
- return undefined;
- }
- monitoringStagingAreaSocketHandler(connection) {
- let self = this;
- let ws = window.multiplexer.channel(connection);
- if (!connection) return;
- self.stagingAreaMonitor = connection;
- ws.onmessage = function(socket) {
- try {
- Utils.checkAuthentication(data.statusCode, function() {
- ws.close();
- });
-
- } catch(e) {
- console.log('An exception occurred in monitoringStagingAreaSocketHandler', e)
- }
- }
- }
-
-}
-
-
-
class TooltipManager {
static addEventListeners(element = document.body) {
- TooltipManager.element = element;
+ if (element === TooltipManager.element) {
+ return;
+ }
+ // remove listeners for current element
TooltipManager.removeEventListeners();
- TooltipManager.element.addEventListener('mousedown', TooltipManager.onScrollRemoveTooltip, true);
- TooltipManager.element.addEventListener('scroll', TooltipManager.onScrollRemoveTooltip, true);
+ TooltipManager.element = element;
+ if (element) {
+ // make sure new element is clean
+ TooltipManager.removeEventListeners();
+ TooltipManager.element.addEventListener('mousedown', TooltipManager.onScrollRemoveTooltip, true);
+ TooltipManager.element.addEventListener('scroll', TooltipManager.onScrollRemoveTooltip, true);
+ }
}
static removeEventListeners() {
return !!UID.from(obj);
}
- static assignUniqueId(obj) {
- return obj[UID.propertyName] = UID.create();
+ static assignUniqueId(obj, uid = null) {
+ if (!obj || /undefined|null/.test(obj)) {
+ return;
+ }
+ uid = uid || UID.create();
+ if (obj.uiState) {
+ obj.uiState[UID.propertyName] = uid;
+ } else {
+ obj[UID.propertyName] = uid;
+ }
}
}
selection.addContainers(containers);
selection.render();
- const edgeBuilder = new DescriptorGraphEdgeBuilder(graph);
- edgeBuilder.addContainers(containers);
- edgeBuilder.render();
-
+ if (!this.props.readOnly) {
+ const edgeBuilder = new DescriptorGraphEdgeBuilder(graph);
+ edgeBuilder.addContainers(containers);
+ edgeBuilder.render();
+ }
const grid = new DescriptorGraphGrid(graph, {size: defaults.snapTo, padding: defaults.padding});
grid.render();
// warn assigning same instance (e.g. pass by reference) so that changes will reflect thru
cpRef.position = source.position;
connectionPointRefList.push(cpRef);
- } catch(e) {
+ } catch (e) {
return;
}
});
test.render();
}
- function drawRelationPointsAndPaths (graph, relationEdges) {
+ function drawRelationPointsAndPaths(graph, relationEdges) {
const paths = graph.paths.selectAll('.relation').data(relationEdges, DescriptorModelFactory.containerIdentity);
paths.enter().append('path')
.attr({
'class': d => {
- return ClassNames('relation', d.type, {'-is-selected': d.uiState && SelectionManager.isSelected(d) /*d.uiState && d.uiState.selected*/});
+ return ClassNames('relation', d.type, {
+ '-is-selected': d.uiState && SelectionManager.isSelected(d) /*d.uiState && d.uiState.selected*/
+ });
},
stroke: 'red',
fill: 'transparent',
// todo extract drag behavior into class DescriptorGraphDrag
const drag = this.drag = d3.behavior.drag()
- .origin(function(d) { return d; })
+ .origin(function (d) {
+ return d;
+ })
.on('drag.graph', function (d) {
uiTransientState.isDragging = true;
const mouse = d3.mouse(graph.g.node());
container.props.descriptorWidth = layoutInfo.width;
container.props.descriptorHeight = layoutInfo.height;
}
- container.dragHandler = drag;
+ if (!props.readOnly) {
+ container.dragHandler = drag;
+ }
container.addContainers(containerList);
return container;
});
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
*/
import _isArray from 'lodash/isArray'
+import _set from 'lodash/set'
+import _get from 'lodash/get'
import guid from '../guid'
import Position from '../graph/Position'
import IconFactory from './IconFactory'
*/
export default class DescriptorModel {
- constructor(model = {uiState: {}}, parent = null) {
+ constructor(model = {uiState: {}}, parent = null, readonly = false) {
// when our instance has no more strong references
// then our properties will get garbage collected.
this._props_ = new WeakMap();
if (parent instanceof DescriptorModel) {
parent.addChild(this);
}
+ this.isReadOnly = readonly;
}
get fieldNames() {
return length !== this[propertyName].length;
}
+ setUiState(setting, path, value){
+ _set(this.uiState, [setting].concat(path), value);
+ }
+
+ getUiState(setting, path, defaultValue){
+ return _get(this.uiState, [setting].concat(path), defaultValue);
+ }
+
}
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
import InternalConnectionPointRef from './descriptors/InternalConnectionPointRef'
import VirtualNetworkFunctionConnectionPoint from './descriptors/VirtualNetworkFunctionConnectionPoint'
import VirtualDeploymentUnitInternalConnectionPoint from './descriptors/VirtualDeploymentUnitInternalConnectionPoint'
-
+import VirtualNetworkFunctionAccessPointMap from './descriptors/VirtualNetworkFunctionAccessPointMap'
function findChildDescriptorModelAndUpdateModel(model, parent) {
if (parent instanceof DescriptorModel) {
const child = parent.findChildByUid(model);
fg.rsp.forEach(rsp => mapRSP(rsp, containerList));
fg.classifier.forEach(classifier => mapClassifier(classifier, containerList));
}
+ function mapConfigParameterMap(ap, containerList) {
+ containerList.push(ap);
+ }
function mapVDU(vdu, containerList) {
containerList.push(vdu);
nsd.constituentVnfd.forEach(cvnfd => mapCVNFD(cvnfd, containerList));
nsd.vld.forEach(vld => mapVLD(vld, containerList));
nsd.vnffgd.forEach(fg => mapFG(fg, containerList));
+ nsd.configParameterMap.forEach(ap => mapConfigParameterMap(ap, containerList));
}
function mapVNFD(vnfd, containerList) {
return findChildDescriptorModelAndUpdateModel(model, parent) || new VirtualLink(model, parent);
}
- static newInternalVirtualLink(model, parent) {
- return findChildDescriptorModelAndUpdateModel(model, parent) || new InternalVirtualLink(model, parent);
+ static newInternalVirtualLink(model, parent, readonly) {
+ return findChildDescriptorModelAndUpdateModel(model, parent) || new InternalVirtualLink(model, parent, readonly);
}
static newPhysicalNetworkFunction(model, parent) {
return findChildDescriptorModelAndUpdateModel(model, parent) || new PhysicalNetworkFunction(model, parent);
}
- static newConstituentVnfdConnectionPoint(model, parent) {
- return findChildDescriptorModelAndUpdateModel(model, parent) || new ConstituentVnfdConnectionPoint(model, parent);
+ static newConstituentVnfdConnectionPoint(model, parent, readonly) {
+ return findChildDescriptorModelAndUpdateModel(model, parent) || new ConstituentVnfdConnectionPoint(model, parent, readonly);
}
static newVnfdConnectionPointRef(model, parent) {
return findChildDescriptorModelAndUpdateModel(model, parent) || new VnfdConnectionPointRef(model, parent);
}
+ static newVirtualNetworkFunctionAccessPointMap(model, parent) {
+ return findChildDescriptorModelAndUpdateModel(model, parent) || new VirtualNetworkFunctionAccessPointMap(model, parent);
+ }
static newForwardingGraph(model, parent) {
return findChildDescriptorModelAndUpdateModel(model, parent) || new ForwardingGraph(model, parent);
}
return obj instanceof VirtualNetworkFunction;
}
+ static isVirtualNetworkFunctionAccessPointMap(obj) {
+ return obj instanceof VirtualNetworkFunctionAccessPointMap;
+ }
static isForwardingGraph(obj) {
return obj instanceof ForwardingGraph;
}
return NetworkService;
}
+ static get VirtualNetworkFunctionAccessPointMap () {
+ return VirtualNetworkFunctionAccessPointMap;
+ }
static get ForwardingGraph () {
return ForwardingGraph;
}
vld: common.concat([]),
vnfd: common.concat(['vdu', 'internal-vld']),
'vnfd.vdu': common.concat(['image', 'image-checksum', 'external-interface', 'vm-flavor', 'cloud-init', 'filename']),
+ 'nsd.config-parameter-map': common.concat([]),
// white-list valid fields to send in the meta field
meta: ['containerPositionMap']
};
const exportInnerTypesMap = {
'constituent-vnfd': 'nsd.constituent-vnfd',
+ 'config-parameter-map': 'nsd.config-parameter-map',
'vdu': 'vnfd.vdu'
};
let keys = Object.keys(data);
if (keys) {
const chosen = this.properties.find(
- c => c.type === 'case' && c.properties && c.properties.some(p => keys.indexOf(p.name) > -1));
+ c => c.type === 'case' && c.properties && c.properties.some(p => keys.indexOf(p.name) > -1 && data[p.name]));
return chosen && serializeAll(chosen.properties, data);
}
return null;
parentMap[':meta'] = parentObj;
const properties = parentObj && parentObj.properties ? parentObj.properties : [];
properties.forEach(p => {
+ const colonIndex = p.name.indexOf(':');
+ if (colonIndex > 0) {
+ p.name = p.name.slice(colonIndex+1);
+ }
parentMap[p.name] = mapProperties({}, assign(p, {
':qualified-type': parentObj[':qualified-type'] + '.' + p.name
}));
return /string|int/.test(property['data-type']) || Property.isEnumeration(property) || Property.isGuid(property);
},
defaultValue(property = {}) {
- if (property.defaultValue) {
- return property.defaultValue;
+ if (property['default-value']) {
+ return property['default-value'];
}
if (this.isObject(property)) {
return {};
if (property.type === 'leaf') {
return defaultValue(property);
}
+ if (property.type === 'leaf_list' ) {
+ return "";
+ }
if (/list/.test(property.type)) {
property.type = 'container';
}
* limitations under the License.
*
*/
-/**
- * Created by onvelocity on 10/20/15.
- */
import DescriptorModelMetaFactory from './DescriptorModelMetaFactory'
return result;
}
}
-export default DescriptorModelSerializer;
\ No newline at end of file
+export default DescriptorModelSerializer;
return 'vnfd.' + ConnectionPoint.type;
}
- constructor(model, parent) {
- super(model, parent);
+ constructor(model, parent, readonly) {
+ super(model, parent, readonly);
this.type = ConnectionPoint.type;
this.uiState['qualified-type'] = ConnectionPoint.qualifiedType;
this.className = 'VirtualNetworkFunctionConnectionPoint';
export default class ConstituentVnfdConnectionPoint extends ConnectionPoint {
- constructor(model, parent) {
- super(model, parent);
+ constructor(model, parent, readonly) {
+ super(model, parent, readonly);
this.uid = parent.uid + this.key;
}
return 'vnfd.' + InternalVirtualLink.type;
}
- constructor(model, parent) {
- super(model, parent);
+ constructor(model, parent, readonly) {
+ super(model, parent, readonly);
this.type = InternalVirtualLink.type;
this.uiState['qualified-type'] = InternalVirtualLink.qualifiedType;
this.className = InternalVirtualLink.className;
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
import ColorGroups from '../../ColorGroups'
import DescriptorModel from '../DescriptorModel'
import ForwardingGraph from './ForwardingGraph'
+import VirtualNetworkFunctionAccessPointMap from './VirtualNetworkFunctionAccessPointMap'
import VirtualLink from './VirtualLink'
import ConstituentVnfd from './ConstituentVnfd'
import PhysicalNetworkFunction from './PhysicalNetworkFunction'
}
+// <<<<<<< Updated upstream
+// get configParameterMap() {
+// if (!this.model['config-parameter-map']) {
+// this.model['config-parameter-map'] = [];
+// }
+// return this.model['config-parameter-map'].map(d => DescriptorModelFactory.newVirtualNetworkFunctionAccessPointMap(d, this)).map((fg, i) => {
+// return fg;
+// });
+// }
+
+// set configParameterMap(obj) {
+// const onVirtualNetworkFunctionAccessPointMap = (fg) => {
+
+// };
+// this.updateModelList('config-parameter-map', obj, VirtualNetworkFunctionAccessPointMap, onVirtualNetworkFunctionAccessPointMap);
+// }
+
+// createConfigParameterMap(model) {
+// model = model || DescriptorModelMetaFactory.createModelInstanceForType('nsd.config-parameter-map');
+// return this.configParameterMap = DescriptorModelFactory.newVirtualNetworkFunctionAccessPointMap(model, this);
+// }
+// =======
+ get configParameterMap() {
+ if (!this.model['config-parameter-map']) {
+ this.model['config-parameter-map'] = [];
+ }
+ return this.model['config-parameter-map'].map(d => DescriptorModelFactory.newVirtualNetworkFunctionAccessPointMap(d, this))
+ }
+
+ set configParameterMap(obj) {
+ this.updateModelList('config-parameter-map', obj, VirtualNetworkFunctionAccessPointMap);
+ }
+
+ createConfigParameterMap() {
+ const model = DescriptorModelMetaFactory.createModelInstanceForType('nsd.config-parameter-map');
+ return this.configParameterMap = DescriptorModelFactory.newVirtualNetworkFunctionAccessPointMap(model, this);
+ }
+// >>>>>>> Stashed changes
+
get vnffgd() {
if (!this.model.vnffgd) {
this.model.vnffgd = [];
}
+
// NOTE temporarily disable NSD connection points
// https://trello.com/c/crVgg2A1/88-do-not-render-nsd-connection-in-the-composer
//get connectionPoint() {
createVld() {
const property = DescriptorModelMetaFactory.getModelMetaForType('vnfd.internal-vld');
- const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(this['internal-vld'], property);
+ const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(this.vld, property);
const model = DescriptorModelMetaFactory.createModelInstanceForType('vnfd.internal-vld', uniqueName);
return this.vld = DescriptorModelFactory.newInternalVirtualLink(model, this);
}
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+/**
+ * Created by onvelocity on 11/23/15.
+ */
+
+'use strict';
+
+import DescriptorModel from '../DescriptorModel'
+import DescriptorModelFactory from '../DescriptorModelFactory'
+
+export default class VnfapMap extends DescriptorModel {
+
+ static get type() {
+ return 'vnfap-map';
+ }
+
+ static get className() {
+ return 'VnfapMap';
+ }
+
+ constructor(model, parent) {
+ super(model, parent);
+ this.type = 'vnfap-map';
+ this.uiState['qualified-type'] = 'nsd.vnfap-map';
+ this.className = 'VnfapMap';
+ // this.addProp('vnfdRef', DescriptorModelFactory.newVirtualNetworkFunctionReadOnlyWrapper({}, this));
+ }
+
+ get id() {
+ return this.model.id;
+ }
+ get capability() {
+ return []
+ }
+
+ get dependency() {
+
+ }
+
+
+}
--- /dev/null
+/*
+ *
+ * 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.
+ *
+ */
+/**
+ * Created by onvelocity on 11/23/15.
+ */
+
+'use strict';
+
+import DescriptorModel from '../DescriptorModel'
+import DescriptorModelFactory from '../DescriptorModelFactory'
+
+export default class ConfigParameterMap extends DescriptorModel {
+
+// <<<<<<< Updated upstream
+// static get type() {
+// return 'config-parameter-map';
+// }
+
+// static get className() {
+// return 'ConfigParameterMap';
+// }
+
+// constructor(model, parent) {
+// super(model, parent);
+// this.type = 'config-parameter-map';
+// this.uiState['qualified-type'] = 'nsd.config-parameter-map';
+// this.className = 'ConfigParameterMap';
+// // this.addProp('vnfdRef', DescriptorModelFactory.newVirtualNetworkFunctionReadOnlyWrapper({}, this));
+// }
+
+// get id() {
+// return this.model.id;
+// }
+// get capability() {
+// return {
+// 'member-vnf-index': this.model.capability['member-vnf-index'],
+// 'capability-ref': this.model.capability['capability-ref'],
+// }
+// }
+// get availableCapabilities() {
+
+// }
+
+// get dependency() {
+
+// }
+// =======
+ static get type() {
+ return 'config-parameter-map';
+ }
+
+ static get className() {
+ return 'ConfigParameterMap';
+ }
+ static get qualifiedType() {
+ return 'nsd.' + ConfigParameterMap.type;
+ }
+ constructor(model, parent) {
+ super(model, parent);
+ this.type = 'config-parameter-map';
+ this.uiState['qualified-type'] = 'nsd.config-parameter-map';
+ this.className = 'ConfigParameterMap';
+ // this.addProp('vnfdRef', DescriptorModelFactory.newVirtualNetworkFunctionReadOnlyWrapper({}, this));
+ }
+
+ get id() {
+ return this.model.id;
+ }
+ // get capability() {
+ // return {
+ // 'member-vnf-index': this.model.capability['member-vnf-index'],
+ // 'capability-ref': this.model.capability['capability-ref'],
+ // }
+ // }
+ // get availableCapabilities() {
+
+ // }
+
+ // get dependency() {
+
+ // }
+
+
+}
export default class VirtualNetworkFunctionReadOnlyWrapper extends DescriptorModel {
constructor(model, parent) {
- super(model, parent);
+ super(model, parent, true);
}
get vld() {
if (!this.model['internal−vld']) {
this.model['internal−vld'] = [];
}
- return this.model['internal−vld'].map(d => DescriptorModelFactory.newInternalVirtualLink(d, this.parent));
+ return this.model['internal−vld'].map(d => DescriptorModelFactory.newInternalVirtualLink(d, this.parent, true));
}
get connectionPoint() {
if (!this.model['connection-point']) {
this.model['connection-point'] = [];
}
- return this.model['connection-point'].map(d => DescriptorModelFactory.newConstituentVnfdConnectionPoint(d, this.parent));
+ return this.model['connection-point'].map(d => DescriptorModelFactory.newConstituentVnfdConnectionPoint(d, this.parent, true));
}
get connectors() {
// last item in path used to assign value on the resolved object
const name = path.pop();
const resolvedObj = path.reduce((r, p, i) => {
- if (typeof(r[p]) !== 'object') {
+ if (typeof (r[p]) !== 'object') {
// look-ahead to see if next path item is a number
const isArray = !isNaN(parseInt(pathCopy[i + 1], 10));
r[p] = isArray ? [] : {}
}, obj);
resolvedObj[name] = value;
},
+ mergePathData(obj, path, data) {
+ path = path.split(/[\.\[\]]/).filter(d => d);
+ // enable look-ahead to determine if type is array or object
+ const pathCopy = path.slice();
+ let resolvedObj = obj;
+ if (path.length) {
+ // last item in path used to assign value on the resolved object
+ const name = path.pop();
+ resolvedObj = path.reduce((r, p, i) => {
+ if (typeof (r[p]) !== 'object') {
+ // look-ahead to see if next path item is a number
+ const isArray = !isNaN(parseInt(pathCopy[i + 1], 10));
+ r[p] = isArray ? [] : {}
+ }
+ return r[p];
+ }, obj)[name];
+ }
+ Object.assign(resolvedObj, data);
+ },
updatePathValue(obj, path, value, isCase) {
// todo: replace implementation of assignPathValue with this impl and
// remove updatePathValue (only need one function, not both)
// look-ahead to see if next path item is a number
const index = parseInt(pathCopy[i + 1], 10);
const isArray = !isNaN(index);
- if (typeof(r[p]) !== 'object') {
+ if (typeof (r[p]) !== 'object') {
r[p] = isArray ? [] : {}
}
if (isRemove && ((i + 1) === path.length)) {
if (isArray) {
r[p] = r[p].filter((d, i) => i !== index);
} else {
- if(isCase) {
+ if (isCase) {
delete r[name];
} else {
delete r[p][name];
}
}
}
- if(isCase) {
+ if (isCase) {
return r;
} else {
return r[p];
},
suffixAsInteger: (field) => {
- return (obj) =>{
+ return (obj) => {
const str = String(obj[field]);
const value = str.replace(str.replace(/[\d]+$/, ''), '');
return 1 + parseInt(value, 10) || 0;
toBiggestValue: (maxIndex, curIndex) => Math.max(maxIndex, curIndex),
- isRelativePath (path) {
+ isRelativePath(path) {
if (path.split('/')[0] == '..') {
return true;
}
return false;
},
- getResults (topLevelObject, pathArray) {
+ getResults(topLevelObject, pathArray) {
let objectCopy = _cloneDeep(topLevelObject);
let i = pathArray.length;
let results = [];
- while(pathArray[pathArray.length - i]) {
+ while (pathArray[pathArray.length - i]) {
if (_isArray(objectCopy[pathArray[pathArray.length - i]])) {
if (i == 2) {
results = _map(objectCopy[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]);
return results;
},
- getAbsoluteResults (topLevelObject, pathArray) {
+ getAbsoluteResults(topLevelObject, pathArray) {
let i = pathArray.length;
let objectCopy = _cloneDeep(topLevelObject);
let results = [];
console.log('Something went wrong while resolving a leafref. Reached a leaf with predicate.');
} else {
// contains no predicate
+ if (!objectCopy) {
+ break;
+ }
results.push(objectCopy[fragment]);
}
}
}
} else {
// contains no predicate
+ if (!objectCopy) {
+ break;
+ }
objectCopy = objectCopy[fragment];
if (!objectCopy) {
// contains no value
return results;
},
- resolveCurrentPredicate (leafRefPath, container, pathCopy) {
+ resolveCurrentPredicate(leafRefPath, container, pathCopy) {
if (leafRefPath.indexOf('current()') != -1) {
// contains current
return fieldKeyArray;
},
- resolveLeafRefPath (catalogs, leafRefPath, fieldKey, path, container) {
+ resolveLeafRefPath(catalogs, leafRefPath, fieldKey, path, container) {
let pathCopy = _clone(path);
// Strip any prefixes
- let leafRefPathCopy = leafRefPath.replace(/[\w\d]*:/g, '');
+ let leafRefPathCopy = leafRefPath.replace(/[-\w]*:/g, '');
// Strip any spaces
leafRefPathCopy = leafRefPathCopy.replace(/\s/g, '');
// Check if relative path or not
// TODO: Below works but
- // better to convert the pathCopy to absolute/rooted path
+ // better to convert the pathCopy to absolute/rooted path
// and use the absolute module instead
if (this.isRelativePath(leafRefPathCopy)) {
let i = pathArray.length;
- while ((pathArray[pathArray.length - i] == '..') && fieldKeyArray.length > 1) {
+ while (pathArray[pathArray.length - i] == '..') {
fieldKeyArray.splice(-1, 1);
if (!isNaN(Number(fieldKeyArray[fieldKeyArray.length - 1]))) {
// found a number, so an index. strip it
if (fieldKeyArray.length == 1) {
for (let key in catalogs) {
for (let subKey in catalogs[key]) {
- let found = _find(catalogs[key][subKey], {id: fieldKeyArray[0]});
+ let found = _find(catalogs[key][subKey], {
+ id: fieldKeyArray[0]
+ });
if (found) {
results = this.getAbsoluteResults(found, pathArray.splice(-i, i));
return results;
for (let key in catalogs) {
for (let subKey in catalogs[key]) {
console.log(key, subKey);
- var found = _find(catalogs[key][subKey], {id: fieldKeyArray[0]});
+ var found = _find(catalogs[key][subKey], {
+ id: fieldKeyArray[0]
+ });
if (found) {
for (let foundKey in found) {
if (_isArray(found[foundKey])) {
- let topLevel = _find(found[foundKey], {id: fieldKeyArray[1]});
+ let topLevel = _find(found[foundKey], {
+ id: fieldKeyArray[1]
+ });
if (topLevel) {
results = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
return results;
} else if (fieldKeyArray.length == 3) {
for (let key in catalogs) {
for (let subKey in catalogs[key]) {
- let found = _find(catalogs[key][subKey], {id: fieldKeyArray[0]});
+ let found = _find(catalogs[key][subKey], {
+ id: fieldKeyArray[0]
+ });
if (found) {
for (let foundKey in found) {
if (_isArray(found[foundKey])) {
- let topLevel = _find(found[foundKey], {id: fieldKeyArray[1]});
+ let topLevel = _find(found[foundKey], {
+ id: fieldKeyArray[1]
+ });
if (topLevel) {
results = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
return results;
}
}
}
-
remote: function(state, catalogType, itemId) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/catalog/' + catalogType + '/' + itemId + '?api_server=' + utils.getSearchParams(window.location).api_server,
+ url: 'api/catalog/' + catalogType + '/' + encodeURIComponent(itemId) + '?api_server=' + utils.getSearchParams(window.location).api_server,
type: 'DELETE',
beforeSend: utils.addAuthorizationStub,
success: function(data) {
});
} else {
$.ajax({
- url: 'api/catalog/' + item.uiState.type + '/' + payload.id + '?api_server=' + utils.getSearchParams(window.location).api_server,
+ url: 'api/catalog/' + item.uiState.type + '/' + encodeURIComponent(payload.id) + '?api_server=' + utils.getSearchParams(window.location).api_server,
type: method,
beforeSend: utils.addAuthorizationStub,
data: payload,
import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions'
import Utils from 'utils/utils.js';
+let API_SERVER = catalogUtils.getSearchParams(window.location).api_server;
+const FILE_SERVER = window.location.hostname === 'localhost'
+ ? API_SERVER : window.location.protocol + '//' + window.location.hostname;
+
+
const getAuthorization = () => 'Basic ' + window.sessionStorage.getItem("auth");
const getStateApiPath = (operation, id) =>
- catalogUtils.getSearchParams(window.location).upload_server + ':4567/api/' + operation + '/' + id + '/state';
+ FILE_SERVER + ':8008/mano/' + operation + '/' + id + '/state';
const getComposerApiPath = (api) =>
window.location.origin + '/composer/api/' + api + '?api_server=' + catalogUtils.getSearchParams(window.location).api_server;
const failHandler = (response) => {
reject(Object.assign({}, operation, FAILED));
};
- const path = getComposerApiPath('package-manager/jobs/' + operation.transactionId);
+ const path = getComposerApiPath('package-copy/jobs/' + operation.transactionId);
ajaxFetch(path, operation, successHandler, failHandler);
});
},
remote: function (state, upload) {
const transactionId = upload.transactionId;
return new Promise(function (resolve, reject) {
- const action = upload.riftAction === 'onboard' ? 'upload' : 'update';
- const path = getStateApiPath(action, transactionId);
+ const action = upload.riftAction === 'onboard' ? 'import' : 'update';
+ const path = getComposerApiPath('package-'+action+'/jobs/' + transactionId);
ajaxFetch(path, upload, resolve, reject);
});
},
import _pick from 'lodash/pick'
import _isEqual from 'lodash/isEqual'
import _cloneDeep from 'lodash/cloneDeep'
+import _merge from 'lodash/merge'
+import _debounce from 'lodash/debounce';
import cc from 'change-case'
import alt from '../alt'
import UID from '../libraries/UniqueId'
import React from 'react'
import DescriptorModel from '../libraries/model/DescriptorModel'
import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
+import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions'
import CatalogDataSourceActions from '../actions/CatalogDataSourceActions'
import CatalogItemsActions from '../actions/CatalogItemsActions'
catalogItemExportGrammars: ['osm', 'tosca']
};
-const areCatalogItemsMetaDataEqual = function (a, b) {
- const metaProps = ['id', 'name', 'short-name', 'description', 'vendor', 'version'];
- const aMetaData = _pick(a, metaProps);
- const bMetaData = _pick(b, metaProps);
- return _isEqual(aMetaData, bMetaData);
+const areCatalogItemsMetaDataEqual = function (catItem, activeItem) {
+ function getDefaultPositionMap() {
+ if (!activeItem.uiState.containerPositionMap) {
+ return activeItem.uiState.containerPositionMap;
+ }
+ let defaultPositionMap = {};
+ defaultPositionMap[activeItem.id] = activeItem.uiState.defaultLayoutPosition;
+ return defaultPositionMap;
+ }
+ const activeItemMetaData = activeItem.uiState.containerPositionMap;
+ const catItemMetaData = catItem.uiState.containerPositionMap;
+ return catItemMetaData === undefined || _isEqual(catItemMetaData, activeItemMetaData);
};
-function createItem (type) {
+function createItem(type) {
let newItem = DescriptorModelMetaFactory.createModelInstanceForType(type);
- if (newItem){
+ if (newItem) {
newItem.id = guid();
- UID.assignUniqueId(newItem.uiState);
+ UID.assignUniqueId(newItem);
newItem.uiState.isNew = true;
newItem.uiState.modified = true;
- newItem.uiState['instance-ref-count'] = 0;
}
return newItem;
}
constructor() {
this.catalogs = defaults.catalogs;
this.isLoading = true;
- this.requiresSave = false;
this.snapshots = {};
this.selectedFormat = defaults.catalogItemExportFormats[0];
this.selectedGrammar = defaults.catalogItemExportGrammars[0];
this.bindActions(CatalogDataSourceActions);
this.bindActions(CatalogItemsActions);
this.exportPublicMethods({
- getCatalogs: this.getCatalogs,
- getCatalogItemById: this.getCatalogItemById,
- getCatalogItemByUid: this.getCatalogItemByUid,
- getTransientCatalogs: this.getTransientCatalogs,
- getTransientCatalogItemById: this.getTransientCatalogItemById,
- getTransientCatalogItemByUid: this.getTransientCatalogItemByUid
- });
+ getCatalogs: this.getCatalogs,
+ getCatalogItemById: this.getCatalogItemById,
+ getCatalogItemByUid: this.getCatalogItemByUid,
+ getTransientCatalogs: this.getTransientCatalogs,
+ getTransientCatalogItemById: this.getTransientCatalogItemById,
+ getTransientCatalogItemByUid: this.getTransientCatalogItemByUid,
+ setUserProfile: this.setUserProfile
+ });
+ this.queueDirtyCheck = _debounce(() => this.saveDirtyDescriptorsToSessionStorage(), 500);
}
resetSelectionState = () => {
return this.catalogs || (this.catalogs = []);
}
+ saveDirtyDescriptorsToSessionStorage() {
+ const dirtyCatalogs = this.catalogs.reduce((result, catalog) => {
+ const dirtyDescriptors = catalog.descriptors.reduce((result, descriptor) => {
+ if (descriptor.uiState.modified && !descriptor.uiState.deleted) {
+ result.push(descriptor);
+ }
+ return result;
+ }, []);
+ if (dirtyDescriptors.length) {
+ let newCatalog = Object.assign({}, catalog);
+ newCatalog.descriptors = dirtyDescriptors;
+ result.push(newCatalog);
+ }
+ return result;
+ }, []);
+ window.sessionStorage.setItem(this.userProfile.userId + '@' + this.userProfile.domain, JSON.stringify({
+ dirtyCatalogs
+ }));
+ }
+
+ mergeDirtyDescriptorsFromSessionStorage(catalogs) {
+ let userProfileDirtyCatalogs = window.sessionStorage.getItem(this.userProfile.userId + '@' + this.userProfile.domain);
+ let dirtyCatalogs = [];
+ if (userProfileDirtyCatalogs) {
+ dirtyCatalogs = JSON.parse(userProfileDirtyCatalogs).dirtyCatalogs;
+ }
+ dirtyCatalogs.forEach((dirtyCatalog) => {
+ let catalog = catalogs.find((c) => c.id === dirtyCatalog.id);
+ dirtyCatalog.descriptors.forEach((dirtyDescriptor, index) => {
+ let descriptor = catalog.descriptors.find((d) => d.id === dirtyDescriptor.id);
+ if (descriptor) {
+ this.addSnapshot(descriptor);
+ _merge(descriptor, dirtyDescriptor);
+ } else {
+ dirtyCatalog.descriptors.splice(index, 1);
+ this.queueDirtyCheck();
+ }
+ })
+ });
+ this.isNotMergedWithSessionStorage = false;
+ return catalogs;
+ }
+
+ setUserProfile = (userProfile) => {
+ if (!this.userProfile) {
+ this.userProfile = userProfile;
+ if (this.catalogs.length) {
+ const catalogs = this.mergeDirtyDescriptorsFromSessionStorage(this.catalogs);
+ this.setState({ catalogs });
+ } else {
+ this.isNotMergedWithSessionStorage = true;
+ }
+ }
+ }
+
getTransientCatalogs() {
return this.state.catalogs || (this.state.catalogs = []);
}
});
return catalog;
});
- updatedCatalogs.filter(d => d.type === 'nsd').forEach(catalog => {
+ updatedCatalogs.filter(d => d.type === 'nsd').forEach(catalog => {
catalog.descriptors = catalog.descriptors.map(descriptor => {
- const instanceRefCount = parseInt(descriptor.uiState['instance-ref-count'], 10);
if (descriptor['constituent-vnfd']) {
descriptor.vnfd = descriptor['constituent-vnfd'].map(d => {
const vnfdId = d['vnfd-id-ref'];
if (!vnfd) {
throw new ReferenceError('no VNFD found in the VNFD Catalog for the constituent-vnfd: ' + d);
}
- if (!isNaN(instanceRefCount) && instanceRefCount > 0) {
- // this will notify user that this item cannot be updated when/if they make a change to it
- vnfd.uiState['instance-ref-count'] = instanceRefCount;
- }
// create an instance of this vnfd to carry transient ui state properties
const instance = _cloneDeep(vnfd);
instance.uiState['member-vnf-index'] = d['member-vnf-index'];
}
return catalog;
});
- this.setState({catalogs: catalogs});
+ this.setState({ catalogs: catalogs });
}
mergeEditsIntoLatestFromServer(catalogsFromServer = []) {
loadCatalogsSuccess(context) {
const fromServer = this.updateCatalogIndexes(context.data);
- const catalogs = this.mergeEditsIntoLatestFromServer(fromServer);
+ let catalogs = this.mergeEditsIntoLatestFromServer(fromServer);
+ if (this.isNotMergedWithSessionStorage) {
+ catalogs = this.mergeDirtyDescriptorsFromSessionStorage(catalogs);
+ }
this.setState({
catalogs: catalogs,
isLoading: false
});
}
- deleteCatalogItemSuccess (response) {
+ deleteCatalogItemSuccess(response) {
let catalogType = response.catalogType;
let itemId = response.itemId;
const catalogs = this.getCatalogs().map(catalog => {
if (catalog.type === catalogType) {
- catalog.descriptors = catalog.descriptors.filter(d => d.id !== itemId);
+ catalog.descriptors = catalog.descriptors.map(d => {
+ // We are just going to mark it as deleted here so it will be hidden from view.
+ // We will let the next catalog refresh actually remove it from the in memory store.
+ // This is to avoid having it reappear because a timing issue with a catalog refresh.
+ // The incoming refresh may still contain the item and it would then reappear till the next refresh.
+ if (d.id === itemId) {
+ d.uiState.deleted = true;
+ const activeItem = ComposerAppStore.getState().item;
+ if (activeItem && activeItem.id === itemId) {
+ ComposerAppActions.showDescriptor.defer();
+ CatalogItemsActions.editCatalogItem.defer(null);
+ }
+ }
+ return d;
+ });
}
return catalog;
});
-
- this.setState({catalogs: catalogs});
+ this.setState({ catalogs: catalogs });
+ this.queueDirtyCheck();
}
- deleteCatalogItemError (data) {
+ deleteCatalogItemError(data) {
console.log('Unable to delete', data.catalogType, 'id:', data.itemId, 'Error:', data.error.responseText);
ComposerAppActions.showError.defer({
- errorMessage: 'Unable to delete ' + data.catalogType + ' id: ' + data.itemId + '. Check if it is in use'
+ errorMessage: 'Unable to delete ' + data.catalogType + ' id: ' + data.itemId + '. Check to see if it is in use.'
});
}
catalogItemMetaDataChanged(item) {
let requiresSave = false;
- const catalogs = this.getCatalogs().map(catalog => {
- if (catalog.id === item.uiState.catalogId) {
- catalog.descriptors = catalog.descriptors.map(d => {
- if (d.id === item.id) {
- // compare just the catalog uiState data (id, name, short-name, description, etc.)
- const modified = !areCatalogItemsMetaDataEqual(d, item);
- if (modified) {
- if (d.uiState['instance-ref-count'] > 0) {
- console.log('cannot edit NSD/VNFD with references to instantiated Network Services');
- ComposerAppActions.showError.defer({
- errorMessage: 'Cannot edit NSD/VNFD with references to instantiated Network Services'
- });
- return _cloneDeep(d);
- } else {
- item.uiState.modified = modified;
- requiresSave = true;
- this.addSnapshot(item);
- }
- }
- return item;
- }
- return d;
- });
- }
- return catalog;
- });
- if (requiresSave) {
- this.setState({catalogs: catalogs, requiresSave: true});
+ let previousVersion = this.getLatestSnapshot(item);
+ // compare just the catalog uiState data
+ const modified = !areCatalogItemsMetaDataEqual(previousVersion, item);
+ if (modified) {
+ item.uiState.modified = true;
+ this.updateCatalogItem(item);
+ this.addSnapshot(item);
+ this.queueDirtyCheck();
}
}
// replace the old descriptor with the updated one
catalog.descriptors = catalog.descriptors.map(d => {
if (d.id === descriptorId) {
- if (d.uiState['instance-ref-count'] > 0) {
- console.log('cannot edit NSD/VNFD with references to instantiated Network Services');
- ComposerAppActions.showError.defer({
- errorMessage: 'Cannot edit NSD/VNFD with references to instantiated Network Services'
- });
- return _cloneDeep(d);
- } else {
- itemDescriptor.model.uiState.modified = true;
- this.addSnapshot(itemDescriptor.model);
- return itemDescriptor.model;
- }
+ itemDescriptor.model.uiState.modified = true;
+ this.addSnapshot(itemDescriptor.model);
+ return itemDescriptor.model;
}
return d;
});
}
return catalog;
});
- this.setState({catalogs: catalogs, requiresSave: true})
+ this.setState({ catalogs: catalogs })
+ this.queueDirtyCheck();
}
deleteSelectedCatalogItem() {
}
deleteCatalogItem(item) {
- const snapshot = JSON.stringify(item);
- function confirmDeleteCancel(event) {
- undo();
- event.preventDefault();
- ModalOverlayActions.hideModalOverlay();
- }
- const remove = () => {
- // item is deleted or does not exist on server, so remove from ui
- this.removeCatalogItem(item);
- this.setState({catalogs: this.getCatalogs()});
- const activeItem = ComposerAppStore.getState().item;
- if (activeItem && activeItem.id === item.id) {
- CatalogItemsActions.editCatalogItem.defer(null);
- }
- ModalOverlayActions.hideModalOverlay();
- };
- const undo = () => {
- // item failed to delete on server so revert ui
- const revertTo = JSON.parse(snapshot);
- this.updateCatalogItem(revertTo);
- const activeItem = ComposerAppStore.getState().item;
- if (activeItem && activeItem.id === revertTo.id) {
- SelectionManager.select(activeItem);
- CatalogItemsActions.editCatalogItem.defer(revertTo);
- SelectionManager.refreshOutline();
- }
- };
if (item) {
- if (item.uiState.isNew) {
- CatalogDataStore.confirmDelete(remove, confirmDeleteCancel);
- } else {
- if (item.uiState['instance-ref-count'] > 0) {
- console.log('cannot delete NSD/VNFD with references to instantiated Network Services');
- ComposerAppActions.showError.defer({
- errorMessage: 'Cannot delete NSD/VNFD with references to instantiated Network Services'
+ CatalogDataStore.confirmDelete(event => {
+ event.preventDefault();
+ ModalOverlayActions.showModalOverlay.defer();
+ this.getInstance().deleteCatalogItem(item.uiState.type, item.id)
+ .then(ModalOverlayActions.hideModalOverlay, ModalOverlayActions.hideModalOverlay)
+ .catch(function () {
+ console.log('overcoming ES6 unhandled rejection red herring');
});
- undo();
- } else {
- const confirmDeleteOK = event => {
- event.preventDefault();
- item.uiState.deleted = true;
- this.setState({catalogs: this.getCatalogs()});
- ModalOverlayActions.showModalOverlay.defer();
- this.getInstance().deleteCatalogItem(item.uiState.type, item.id)
- .then(remove, undo)
- .then(ModalOverlayActions.hideModalOverlay, ModalOverlayActions.hideModalOverlay)
- .catch(function() {
- console.log('overcoming ES6 unhandled rejection red herring');
- });
- };
- CatalogDataStore.confirmDelete(confirmDeleteOK, confirmDeleteCancel);
- }
- }
+ }, );
}
}
static confirmDelete(onClickYes, onClickCancel) {
+ const cancelDelete = onClickCancel || (e => ModalOverlayActions.hideModalOverlay.defer());
ModalOverlayActions.showModalOverlay.defer((
<div className="actions panel">
<div className="panel-header">
</div>
<div className="panel-body">
<a className="action confirm-yes primary-action Button" onClick={onClickYes}>Yes, delete selected catalog item</a>
- <a className="action cancel secondary-action Button" onClick={onClickCancel}>No, cancel</a>
+ <a className="action cancel secondary-action Button" onClick={cancelDelete}>No, cancel</a>
</div>
</div>
));
if (!this.snapshots[item.id]) {
this.snapshots[item.id] = [];
}
+ // save the snapshot with a new id for an in memory instance
+ let uid = UID.from(item);
+ UID.assignUniqueId(item);
this.snapshots[item.id].push(JSON.stringify(item));
+ UID.assignUniqueId(item, uid);
+ }
+ }
+
+ getLatestSnapshot(item) {
+ if (this.snapshots[item.id]) {
+ return JSON.parse(this.snapshots[item.id][this.snapshots[item.id].length - 1]);
}
+ this.getCatalogs().forEach(catalog => {
+ if (catalog.id === item.uiState.catalogId) {
+ catalog.descriptors.forEach(d => {
+ if (d.id === item.id) {
+ return d;
+ }
+ });
+ }
+ });
+ return {};
}
resetSnapshots(item) {
});
return catalog;
});
- this.setState({catalogs: catalogs});
+ this.setState({ catalogs: catalogs });
this.catalogItemMetaDataChanged(item);
}
}
this.updateCatalogItem(revertTo);
// TODO should the cancel action clear the undo/redo stack back to the beginning?
this.resetSnapshots(revertTo);
- this.setState({requiresSave: false});
CatalogItemsActions.editCatalogItem.defer(revertTo);
+ this.queueDirtyCheck();
}
}
}
saveItem(item) {
if (item) {
- if (item.uiState['instance-ref-count'] > 0) {
- console.log('cannot save NSD/VNFD with references to instantiated Network Services');
- ComposerAppActions.showError.defer({
- errorMessage: 'Cannot save NSD/VNFD with references to instantiated Network Services'
- });
- return;
- }
const success = () => {
delete item.uiState.modified;
if (item.uiState.isNew) {
this.resetSnapshots(item);
ModalOverlayActions.hideModalOverlay.defer();
CatalogItemsActions.editCatalogItem.defer(item);
+ this.queueDirtyCheck();
};
const failure = () => {
ModalOverlayActions.hideModalOverlay.defer();
this.resetSelectionState();
}
}
- saveCatalogItemError(data){
- let error = JSON.parse(data.error.responseText);
- const errorMsg = error && error.body && error.body['rpc-reply'] && JSON.stringify(error.body['rpc-reply']['rpc-error'], null, ' ')
+ saveCatalogItemError(data) {
+ const descriptor = this.getCatalogs().reduce((gotIt, catalog) => {
+ if (!gotIt && (catalog.type === data.catalogType)) {
+ return catalog.descriptors.find(d => {
+ if (d.id === data.itemId) {
+ return d;
+ }
+ });
+ }
+ return gotIt;
+ }, null);
+ const container = data.catalogType === 'nsd' ?
+ DescriptorModelFactory.newNetworkService(descriptor, null)
+ : DescriptorModelFactory.newVirtualNetworkFunction(descriptor, null)
ComposerAppActions.showError.defer({
- errorMessage: 'Unable to save the descriptor.\n' + errorMsg
+ errorMessage: 'Unable to save the descriptor.',
+ rpcError: data.error.responseText
});
+ ComposerAppActions.recordDescriptorError.defer({
+ descriptor: container,
+ type: data.catalogType,
+ id: data.itemId,
+ error: data.error.responseText
+ })
}
}
export default alt.createStore(CatalogDataStore, 'CatalogDataStore');
+
const exception = function ignoreException() {};
const packagePropertyNames = Object.keys(defaults.downloadPackage);
+const API_SERVER = utils.getSearchParams(window.location).api_server;
+const FILE_SERVER = window.location.hostname === 'localhost'
+ ? API_SERVER : window.location.protocol + '//' + window.location.hostname;
function getCatalogPackageManagerServerOrigin() {
- return utils.getSearchParams(window.location).upload_server + ':4567';
+ return FILE_SERVER + ':8008';
}
function delayStatusCheck(statusCheckFunction, operation) {
break;
case 'upload-success':
statusInfo.pending = true;
- statusInfo.progress = 100;
+ statusInfo.progress = 50;
statusInfo.message = 'Upload completed.';
statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || operation.transactionId;
break;
case 'pending':
statusInfo.pending = true;
statusInfo.progress = 50;
- statusInfo.message = responseData.events[responseData.events.length - 1].text;
+ statusInfo.message = responseData.events && responseData.events.length && responseData.events[responseData.events.length - 1].text;
break;
case 'success':
statusInfo.success = true;
if (operation.type === 'download') {
statusInfo.urlValidUntil = moment().add(defaults.downloadUrlTimeToLiveInMinutes, 'minutes').toISOString();
if (responseData.filename) {
- statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + responseData.filename;
+ statusInfo.url = getCatalogPackageManagerServerOrigin() + '/mano/export/' + responseData.filename;
} else {
- statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + operation.transactionId + '.tar.gz';
+ statusInfo.url = getCatalogPackageManagerServerOrigin() + '/mano/export/' + operation.transactionId + '.tar.gz';
}
}
break;
statusInfo.error = true;
statusInfo.message = responseData.errors[0].value;
break;
+ case 'COMPLETED':
+ statusInfo.success = true;
+ statusInfo.progress = 100;
+ statusInfo.message = "Onboarding completed";
+ break;
+ case 'IN_PROGRESS':
+ statusInfo.pending = true;
+ statusInfo.progress = (operation.progress || 25) + ((100 - operation.progress) / 2);
+ statusInfo.message = "Onboarding in progress";
+ break;
+ case 'FAILED':
+ statusInfo.error = true;
+ statusInfo.message = "Operation failed";
+ break;
default:
- throw new ReferenceError('a status of "request", "success", "failure", "pending", "upload-completed", "upload-error", "download-requested", "upload-progress", "upload-action" is required');
+ statusInfo.error = true;
+ statusInfo.message = responseData.message || "Operation failed";
}
} else {
// typically get here due to unexpected development errors (backend exceptions, down/up load server access issues)
-
/*
*
* Copyright 2016 RIFT.IO Inc
import _isNumber from 'lodash/isNumber'
import _cloneDeep from 'lodash/cloneDeep'
import _isEmpty from 'lodash/isEmpty'
+import _isArray from 'lodash/isArray'
import _mergeWith from 'lodash/mergeWith'
import _uniqBy from 'lodash/uniqBy'
import _isEqual from 'lodash/isEqual'
import _findIndex from 'lodash/findIndex'
import _remove from 'lodash/remove'
+import _get from 'lodash/get';
+import _set from 'lodash/set';
import d3 from 'd3'
import alt from '../alt'
import UID from '../libraries/UniqueId'
+import DescriptorModel from '../libraries/model/DescriptorModel'
import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
import PanelResizeAction from '../actions/PanelResizeAction'
import CatalogItemsActions from '../actions/CatalogItemsActions'
import CanvasEditorActions from '../actions/CanvasEditorActions'
+import DescriptorEditorActions from '../actions/DescriptorEditorActions'
import ComposerAppActions from '../actions/ComposerAppActions'
import CatalogFilterActions from '../actions/CatalogFilterActions'
import CanvasPanelTrayActions from '../actions/CanvasPanelTrayActions'
import FileManagerSource from '../components/filemanager/FileManagerSource';
import FileManagerActions from '../components/filemanager/FileManagerActions';
+import Property from '../libraries/model/DescriptorModelMetaProperty'
+import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
+
import React from 'react';
//Hack for crouton fix. Should eventually put composer in skyquake alt context
import utils from '../libraries/utils';
class ComponentBridge extends React.Component {
- constructor(props) {
- super(props);
- NotificationError = this.props.flux.actions.global.showNotification;
- }
- render(){
- return <i></i>
- }
+ constructor(props) {
+ super(props);
+ NotificationError = this.props.flux.actions.global.showNotification;
+ }
+ render() {
+ return <i > </i>
+ }
}
const getDefault = (name, defaultValue) => {
const val = window.localStorage.getItem('defaults-' + name);
class ComposerAppStore {
constructor() {
- //Bridge for crouton fix
- this.ComponentBridgeElement = SkyquakeComponent(ComponentBridge);
+ //Bridge for crouton fix
+ this.ComponentBridgeElement = SkyquakeComponent(ComponentBridge);
this.exportAsync(FileManagerSource)
// the catalog item currently being edited in the composer
this.drag = null;
this.message = '';
this.messageType = '';
+ this.showHelp = { onFocus: false, forAll: true, forNothing: false },
+ this.detailPanel = { collapsed: true, hidden: false }
this.showJSONViewer = false;
this.showClassifiers = {};
this.editPathsMode = false;
this.downloadJobs = {};
this.containers = [];
this.newPathName = '';
+ this.displayedPanel = 'forwarding'; //or parameter
//End File manager values
this.bindListeners({
onResize: PanelResizeAction.RESIZE,
editCatalogItem: CatalogItemsActions.EDIT_CATALOG_ITEM,
- catalogItemMetaDataChanged: CatalogItemsActions.CATALOG_ITEM_META_DATA_CHANGED,
catalogItemDescriptorChanged: CatalogItemsActions.CATALOG_ITEM_DESCRIPTOR_CHANGED,
toggleShowMoreInfo: CanvasEditorActions.TOGGLE_SHOW_MORE_INFO,
showMoreInfo: CanvasEditorActions.SHOW_MORE_INFO,
addVirtualDeploymentDescriptor: CanvasEditorActions.ADD_VIRTUAL_DEPLOYMENT_DESCRIPTOR,
selectModel: ComposerAppActions.SELECT_MODEL,
outlineModel: ComposerAppActions.OUTLINE_MODEL,
+ modelSaveError: ComposerAppActions.RECORD_DESCRIPTOR_ERROR,
showError: ComposerAppActions.SHOW_ERROR,
clearError: ComposerAppActions.CLEAR_ERROR,
setDragState: ComposerAppActions.SET_DRAG_STATE,
openDownloadMonitoringSocketSuccess: FileManagerActions.openDownloadMonitoringSocketSuccess,
getFilelistSocketSuccess: FileManagerActions.getFilelistSocketSuccess,
newPathNameUpdated: FileManagerActions.newPathNameUpdated,
- createDirectory: FileManagerActions.createDirectory
+ createDirectory: FileManagerActions.createDirectory,
+ modelSetOpenState: DescriptorEditorActions.setOpenState,
+ modelError: DescriptorEditorActions.setError,
+ modelSet: DescriptorEditorActions.setValue,
+ modelAssign: DescriptorEditorActions.assignValue,
+ modelListNew: DescriptorEditorActions.addListItem,
+ modelListRemove: DescriptorEditorActions.removeListItem,
+ showHelpForNothing: DescriptorEditorActions.showHelpForNothing,
+ showHelpForAll: DescriptorEditorActions.showHelpForAll,
+ showHelpOnFocus: DescriptorEditorActions.showHelpOnFocus,
+ collapseAllPanels: DescriptorEditorActions.collapseAllPanels,
+ expandAllPanels: DescriptorEditorActions.expandAllPanels,
+ modelForceOpenState: DescriptorEditorActions.expandPanel,
+ showPanelsWithData: DescriptorEditorActions.showPanelsWithData
+
});
- this.exportPublicMethods({
- closeFileManagerSockets: this.closeFileManagerSockets.bind(this)
- })
+ this.exportPublicMethods({
+ closeFileManagerSockets: this.closeFileManagerSockets.bind(this)
+ })
}
onResize(e) {
+ const layout = Object.assign({}, this.layout);
if (e.type === 'resize-manager.resize.catalog-panel') {
- const layout = Object.assign({}, this.layout);
layout.left = Math.max(0, layout.left - e.moved.x);
if (layout.left !== this.layout.left) {
- this.setState({layout: layout});
+ this.setState({
+ layout: layout
+ });
}
} else if (e.type === 'resize-manager.resize.details-panel') {
- const layout = Object.assign({}, this.layout);
layout.right = Math.max(0, layout.right + e.moved.x);
if (layout.right !== this.layout.right) {
- this.setState({layout: layout});
+ this.setState({
+ layout: layout
+ });
}
} else if (/^resize-manager\.resize\.canvas-panel-tray/.test(e.type)) {
- const layout = Object.assign({}, this.layout);
layout.bottom = Math.max(25, layout.bottom + e.moved.y);
if (layout.bottom !== this.layout.bottom) {
- const zoom = autoZoomCanvasScale(layout.bottom) ;
+ const zoom = autoZoomCanvasScale(layout.bottom);
if (this.zoom !== zoom) {
- this.setState({layout: layout, zoom: zoom});
+ this.setState({
+ layout: layout,
+ zoom: zoom
+ });
} else {
- this.setState({layout: layout});
+ this.setState({
+ layout: layout
+ });
}
}
- } else if (e.type !== 'resize') {
+ } else if (e.type == 'resize') {
+ layout.height = e.target.innerHeight;
+ this.setState({
+ layout: layout
+ });
+ } else {
console.log('no resize handler for ', e.type, '. Do you need to add a handler in ComposerAppStore::onResize()?')
}
SelectionManager.refreshOutline();
const self = this;
let containers = [];
let cpNumber = 0;
- if(!document.body.classList.contains('resizing')) {
+ if (!document.body.classList.contains('resizing')) {
containers = [item].reduce(DescriptorModelFactory.buildCatalogItemFactory(CatalogDataStore.getState().catalogs), []);
containers.filter(d => DescriptorModelFactory.isConnectionPoint(d)).forEach(d => {
d.cpNumber = ++cpNumber;
containers.filter(d => DescriptorModelFactory.isVnfdConnectionPointRef(d)).filter(ref => ref.key === d.key).forEach(ref => ref.cpNumber = d.cpNumber);
});
- this.setState({containers: containers, item: _cloneDeep(item)});
+ this.setState({
+ containers: containers,
+ item: _cloneDeep(item)
+ });
}
SelectionManager.refreshOutline();
}
this.openFileManagerSockets(item);
}
}
- catalogItemMetaDataChanged(item) {
- this.updateItem(item);
- }
catalogItemDescriptorChanged(itemDescriptor) {
- this.catalogItemMetaDataChanged(itemDescriptor.model);
+ this.updateItem(itemDescriptor.model);
+ }
+
+ setItemError(descriptor, path, message) {
+ // save locally and globally
+ descriptor.setUiState('error', path, message);
+ if (descriptor.parent) {
+ let parentPath = [];
+ while (descriptor.parent) {
+ parentPath.push(descriptor.type);
+ const index = descriptor.parent.model[descriptor.type].findIndex(item => item.id === descriptor.id);
+ parentPath.push(index);
+ descriptor = descriptor.parent;
+ }
+ parentPath.length && descriptor.setUiState('error', parentPath.concat(path), message);
+ } else if (path.length > 1 && descriptor[path[0]]) {
+ // if we are indirectly editing a sub model set the error state in the sub model
+ descriptor[path[0]][path[1]].setUiState('error', path.slice(2), message);
+ }
+ }
+
+ modelSaveError(input) {
+ const { descriptor, type, id } = input;
+ const errorMessage = {
+ 'data-missing': "Required value.",
+ 'missing-element': "Incomplete configuration."
+ }
+ if (descriptor.id === id) {
+ let error = input.error;
+ let rpcError = null;
+ if (typeof error === 'string') {
+ try {
+ error = JSON.parse(error);
+ } catch (e) {
+ error = {};
+ }
+ }
+ rpcError = error['rcp-error']
+ || (error['rcp-reply'] && error['rcp-reply']['rcp-error'])
+ || (error.body && error.body['rpc-reply'] && error.body['rpc-reply']['rpc-error']);
+ if (rpcError) {
+ const errorTag = rpcError['error-tag'];
+ const message = errorMessage[errorTag] || errorTag;
+ let errPath = rpcError['error-path'].trim();
+ errPath = errPath.replace(/[-\w]*:/g, '');
+ errPath = errPath.slice(errPath.indexOf('/' + type + '[') + 1);
+ const path = [];
+ let splitIndex = errPath.indexOf('/');
+ function ripOutNodeExpression(str, complexNode) {
+ const expressionEnd = str.indexOf(']');
+ complexNode.push(str.slice(1, expressionEnd));
+ if (str.charAt(expressionEnd + 1) === '[') {
+ str = str.slice(expressionEnd + 1);
+ return ripOutNodeExpression(str, complexNode)
+ }
+ return str.slice(expressionEnd + 2);
+ }
+ while (splitIndex > -1) {
+ const expressionStart = errPath.indexOf('[');
+ if (expressionStart > 0 && expressionStart < splitIndex) {
+ const complexNode = [];
+ complexNode.push(errPath.slice(0, expressionStart));
+ errPath = errPath.slice(expressionStart);
+ errPath = ripOutNodeExpression(errPath, complexNode);
+ path.push(complexNode);
+ } else {
+ path.push(errPath.slice(0, splitIndex))
+ errPath = errPath.slice(splitIndex + 1);
+ }
+ splitIndex = errPath.indexOf('/');
+ }
+ const expressionStart = errPath.indexOf('[');
+ if (expressionStart > 0) {
+ const complexNode = [];
+ complexNode.push(errPath.slice(0, expressionStart));
+ errPath = errPath.slice(expressionStart);
+ errPath = ripOutNodeExpression(errPath, complexNode);
+ } else {
+ path.push(errPath.slice(0))
+ }
+ let model = descriptor.model;
+ path.shift();
+ let fullPath = path.reduce((a, p, i) => {
+ let element = p;
+ let subPath = [];
+ if (Array.isArray(p)) {
+ element = p.shift();
+ subPath = p;
+ }
+ a.push(element);
+ model = model[element];
+ const match = subPath.reduce((m, e) => {
+ const id = e.split('=');
+ const key = id[0];
+ let value = id[1];
+ value = value.charAt(0) === "'" ? value.split("'")[1] : value;
+ m.push({ key, value });
+ return m;
+ }, []);
+ if (match.length) {
+ const index = model.findIndex(obj => match.every(e => obj[e.key] == e.value));
+ a.push(index);
+ model = model[index];
+ }
+ return a;
+ }, []);
+ this.setItemError(descriptor, fullPath, message);
+ this.updateItem(descriptor.getRoot().model);
+ }
+ }
+ }
+
+ modelError(input) {
+ const { descriptor, path, message } = input;
+ this.setItemError(descriptor, path, message);
+ this.updateItem(descriptor.getRoot().model)
+ }
+
+ modelSet(input) {
+ const { descriptor, path, value } = input;
+ _set(descriptor.model, path, value);
+ this.setItemError(descriptor, path, null);
+ this.updateItem(descriptor.getRoot().model)
+ CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
+ }
+
+ modelSetOpenState(input) {
+ const { descriptor, path, isOpen } = input;
+ descriptor.setUiState('opened', path, isOpen);
+ this.updateItem(descriptor.getRoot().model)
+ }
+
+ modelForceOpenState(input) {
+ const { descriptor, path } = input;
+ let openPath = [];
+ const targetPath = _isArray(path) ? path : [path];
+ targetPath.forEach(p => {
+ openPath.push(p);
+ descriptor.setUiState('opened', openPath, true);
+ });
+ this.updateItem(descriptor.getRoot().model)
+ }
+
+ modelAssign(input) {
+ const { descriptor, path, source } = input;
+ let obj = _get(descriptor.model, path) || {};
+ Object.assign(obj, source);
+ this.updateItem(descriptor.getRoot().model)
+ CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
+ }
+
+ modelListNew(input) {
+ const { descriptor, path, property } = input;
+ const create = Property.getContainerCreateMethod(property, descriptor);
+ if (create) {
+ const model = null;
+ create(model, path, property);
+ } else {
+ // get a unique name for the new list item based on the current list content
+ // some lists, based on the key, may not get a uniqueName generated here
+ const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(descriptor.model[property.name], property);
+ const value = Property.createModelInstance(property, uniqueName);
+ let list = _get(descriptor.model, path) || [];
+ list.push(value);
+ _set(descriptor.model, path, list);
+ }
+ const list = _get(descriptor.model, path);
+ let openPath = path.slice();
+ descriptor.setUiState('opened', openPath, true);
+ openPath.push((list.length - 1).toString());
+ descriptor.setUiState('opened', openPath, true);
+ function traverseProps(properties) {
+ properties.forEach((p) => {
+ if (p.type === 'list' || p.type === 'container') {
+ openPath.push(p.name);
+ descriptor.setUiState('opened', openPath, true);
+ p.type === 'container' && traverseProps(p.properties);
+ openPath.pop();
+ }
+ });
+ }
+ traverseProps(property.properties);
+ this.updateItem(descriptor.getRoot().model);
+ CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
+ }
+
+ modelListRemove(input) {
+ const { descriptor, path, property } = input;
+ const removeMethod = Property.getContainerMethod(property, descriptor, 'remove');
+ if (removeMethod) {
+ removeMethod(_get(descriptor.model, path));
+ } else {
+ const index = path.pop();
+ let list = _get(descriptor.model, path);
+ list = list.splice(index, 1);
+ }
+ this.updateItem(descriptor.getRoot().model);
+ CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
+ }
+
+ showHelpForNothing() {
+ this.setState({ showHelp: { onFocus: false, forAll: false, forNothing: true } })
+ }
+
+ showHelpForAll() {
+ this.setState({ showHelp: { onFocus: false, forAll: true, forNothing: false } })
+ }
+
+ showHelpOnFocus() {
+ this.setState({ showHelp: { onFocus: true, forAll: false, forNothing: false } })
+ }
+
+ clearOpenPanelState(descriptor) {
+ if (descriptor) {
+ descriptor.setUiState('opened', [], {});
+ this.updateItem(descriptor.getRoot().model);
+ }
+ }
+ collapseAllPanels(input) {
+ this.setState({ openPanelsWithData: false, collapsePanelsByDefault: true });
+ this.clearOpenPanelState(input.descriptor);
+ }
+
+ expandAllPanels(input) {
+ this.setState({ openPanelsWithData: false, collapsePanelsByDefault: false });
+ this.clearOpenPanelState(input.descriptor);
+ }
+
+ showPanelsWithData(input) {
+ this.setState({ openPanelsWithData: true, collapsePanelsByDefault: true });
+ this.clearOpenPanelState(input.descriptor);
}
showMoreInfo() {
- this.setState({showMore: true});
+ this.setState({
+ showMore: true
+ });
}
showLessInfo() {
- this.setState({showMore: false});
+ this.setState({
+ showMore: false
+ });
}
showError(data) {
- NotificationError.defer({msg: data.errorMessage, type: 'error'})
- // this.setState({message: data.errorMessage, messageType: 'error'});
+ NotificationError.defer({
+ msg: data.errorMessage,
+ type: 'error',
+ rpcError: data.rpcError
+ })
+ // this.setState({message: data.errorMessage, messageType: 'error'});
}
clearError() {
- this.setState({message: '', messageType: ''});
+ this.setState({
+ message: '',
+ messageType: ''
+ });
}
toggleShowMoreInfo() {
- this.setState({showMore: !this.showMore});
+ this.setState({
+ showMore: !this.showMore
+ });
}
applyDefaultLayout() {
if (this.item && this.item.uiState && this.item.uiState.containerPositionMap) {
if (!_isEmpty(this.item.uiState.containerPositionMap)) {
this.item.uiState.containerPositionMap = {};
+ this.updateItem(this.item);
CatalogItemsActions.catalogItemMetaDataChanged.defer(this.item);
}
}
}
+ updateItemNotifyLetSettleCheckMetaData(descriptor) {
+ this.updateItem(descriptor.model);
+ CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor);
+ setTimeout(() => CatalogItemsActions.catalogItemMetaDataChanged(this.item), 100);
+ }
+
addVirtualLinkDescriptor(dropCoordinates = null) {
let vld;
if (this.item) {
vld.uiState.dropCoordinates = dropCoordinates;
SelectionManager.clearSelectionAndRemoveOutline();
SelectionManager.addSelection(vld);
- this.updateItem(vld.getRoot().model);
- CatalogItemsActions.catalogItemDescriptorChanged.defer(vld.getRoot());
+ this.updateItemNotifyLetSettleCheckMetaData(vld.getRoot());
}
}
}
fg.uiState.dropCoordinates = dropCoordinates;
SelectionManager.clearSelectionAndRemoveOutline();
SelectionManager.addSelection(fg);
- this.updateItem(nsdc.model);
- CatalogItemsActions.catalogItemDescriptorChanged.defer(nsdc);
+ this.updateItemNotifyLetSettleCheckMetaData(nsdc);
}
}
vdu.uiState.dropCoordinates = dropCoordinates;
SelectionManager.clearSelectionAndRemoveOutline();
SelectionManager.addSelection(vdu);
- this.updateItem(vdu.getRoot().model);
- CatalogItemsActions.catalogItemDescriptorChanged.defer(vdu.getRoot());
+ this.updateItemNotifyLetSettleCheckMetaData(vdu.getRoot());
}
}
selectModel(container) {
if (SelectionManager.select(container)) {
const model = DescriptorModelFactory.isContainer(container) ? container.getRoot().model : container;
- this.catalogItemMetaDataChanged(model);
+ this.updateItem(model);
}
}
clearSelection() {
SelectionManager.clearSelectionAndRemoveOutline();
- this.catalogItemMetaDataChanged(this.item);
}
setDragState(dragState) {
- this.setState({drag: dragState});
+ this.setState({
+ drag: dragState
+ });
}
filterCatalogByType(typeValue) {
- this.setState({filterCatalogByTypeValue: typeValue})
+ this.setState({
+ filterCatalogByTypeValue: typeValue
+ })
}
setCanvasZoom(zoom) {
- this.setState({zoom: zoom});
+ this.setState({
+ zoom: zoom
+ });
}
showJsonViewer() {
- this.setState({showJSONViewer: true});
+ this.setState({
+ showJSONViewer: true
+ });
}
closeJsonViewer() {
- this.setState({showJSONViewer: false});
+ this.setState({
+ showJSONViewer: false
+ });
}
- toggleCanvasPanelTray() {
+ toggleCanvasPanelTray(event) {
const layout = this.layout;
- if (layout.bottom > 25) {
+ const attrMap = event.target.attributes;
+ let panelEvent = null;
+ for (let k in attrMap) {
+ if (attrMap[k].name == 'data-event') {
+ panelEvent = attrMap[k].nodeValue;
+ }
+ }
+ if ((layout.bottom > 25) && ((panelEvent == this.displayedPanel) || panelEvent == 'arrow')) {
this.closeCanvasPanelTray();
} else {
this.openCanvasPanelTray();
}
+ if (panelEvent != 'arrow') {
+ this.setState({
+ displayedPanel: panelEvent
+ })
+ }
}
openCanvasPanelTray() {
};
const zoom = defaults.defaultPanelTrayOpenZoom;
if (this.zoom !== zoom) {
- this.setState({layout: layout, zoom: zoom, restoreZoom: this.zoom});
+ this.setState({
+ layout: layout,
+ zoom: zoom,
+ restoreZoom: this.zoom
+ });
} else {
- this.setState({layout: layout});
+ this.setState({
+ layout: layout
+ });
}
}
};
const zoom = this.restoreZoom || autoZoomCanvasScale(layout.bottom);
if (this.zoom !== zoom) {
- this.setState({layout: layout, zoom: zoom, restoreZoom: null});
+ this.setState({
+ layout: layout,
+ zoom: zoom,
+ restoreZoom: null
+ });
} else {
- this.setState({layout: layout, restoreZoom: null});
+ this.setState({
+ layout: layout,
+ restoreZoom: null
+ });
}
}
*/
const eventNames = ['fullscreenchange', 'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'];
- const appRoot = document.body;//.getElementById('RIFT_wareLaunchpadComposerAppRoot');
+ const appRoot = document.body; //.getElementById('RIFT_wareLaunchpadComposerAppRoot');
const comp = this;
uiTransientState.restoreLayout = restoreLayout;
layout.left = 0;
layout.right = 0;
- comp.setState({fullScreenMode: true, layout: layout, restoreLayout: restoreLayout});
+ comp.setState({
+ fullScreenMode: true,
+ layout: layout,
+ restoreLayout: restoreLayout
+ });
} else {
- comp.setState({fullScreenMode: false, layout: uiTransientState.restoreLayout});
+ comp.setState({
+ fullScreenMode: false,
+ layout: uiTransientState.restoreLayout
+ });
}
}
document.webkitExitFullscreen();
}
- this.setState({fullScreenMode: false});
+ this.setState({
+ fullScreenMode: false
+ });
}
showAssets() {
getFilelistSuccess(data) {
let self = this;
let filesState = null;
- if (self.fileMonitoringSocketID) {
- let newState = {};
- if(data.hasOwnProperty('contents')) {
- filesState = updateFileState( _cloneDeep(this.filesState),data);
+ if (self.fileMonitoringSocketID) {
+ let newState = {};
+ if (data.hasOwnProperty('contents')) {
+ filesState = updateFileState(_cloneDeep(this.filesState), data);
let normalizedData = normalizeTree(data);
newState = {
files: {
- data: _mergeWith(normalizedData.data, self.files.data, function(obj, src) {
- return _uniqBy(obj? obj.concat(src) : src, 'name');
+ data: _mergeWith(normalizedData.data, self.files.data, function (obj, src) {
+ return _uniqBy(obj ? obj.concat(src) : src, 'name');
}),
id: normalizedData.id
},
filesState: filesState
}
- } else {
- newState = {
- files: false
- }
- }
- if(!_isEqual(newState.files, this.files) || ! _isEqual(newState.fileState, this.fileState)) {
- this.setState(newState);
- }
-
- }
+ } else {
+ newState = {
+ files: false
+ }
+ }
+ if (!_isEqual(newState.files, this.files) || !_isEqual(newState.fileState, this.fileState)) {
+ this.setState(newState);
+ }
+
+ }
+
function normalizeTree(data) {
let f = {
- id:[],
- data:{}
+ id: [],
+ data: {}
};
+
function getContents(d) {
- if(d.hasOwnProperty('contents')) {
+ if (d.hasOwnProperty('contents')) {
let contents = [];
- d.contents.map(function(c,i) {
+ d.contents.map(function (c, i) {
if (!c.hasOwnProperty('contents')) {
contents.push(c);
} else {
}
}
getContents(data);
- return f;
+ return f;
}
+
function updateFileState(obj, d) {
d.newFile = '';
- if(d.hasOwnProperty('contents')) {
+ if (d.hasOwnProperty('contents')) {
d.contents.map(updateFileState.bind(null, obj))
}
// override any "pending" state we may have initialized
});
}
addFileSuccess = (data) => {
- if(!data.refresh) {
+ if (!data.refresh) {
let path = data.path;
if (path.startsWith('readme')) {
// this asset type stuff should be in a more common location
let assetGroup = files.data[path] || [];
if (fileName) {
let name = path + '/' + fileName;
- if (assetGroup.findIndex(f => f.name === name) == -1){
- assetGroup.push({name});
+ if (assetGroup.findIndex(f => f.name === name) == -1) {
+ assetGroup.push({
+ name
+ });
}
}
files.data[path] = assetGroup;
- if (files.id.indexOf(path) == -1){
+ if (files.id.indexOf(path) == -1) {
files.id.push(path);
}
let filesState = _cloneDeep(this.filesState);
filesState[name] = "DOWNLOADING";
- this.setState({files, filesState});
+ this.setState({
+ files,
+ filesState
+ });
}
}
startWatchingJob = () => {
let ws = window.multiplexer.channel(this.jobSocketId);
this.setState({
- jobSocket:null
+ jobSocket: null
})
}
openDownloadMonitoringSocketSuccess = (id) => {
let downloadJobs = _cloneDeep(self.downloadJobs);
let newFiles = false;
ws.onmessage = (socket) => {
- if (self.files && self.files.length > 0) {
- let jobs = [];
- try {
- jobs = JSON.parse(socket.data);
- } catch(e) {}
- newFiles = _cloneDeep(self.files);
- jobs.map(function(j) {
- //check if not in completed state
- let fullPath = j['package-path'];
- let path = fullPath.split('/');
- let fileName = path.pop();
- path = path.join('/');
- let index = _findIndex(self.files.data[path], function(o){
- return fullPath == o.name
- });
- if((index > -1) && newFiles.data[path][index]) {
- newFiles.data[path][index].status = j.status
+ if (self.files && self.files.length > 0) {
+ let jobs = [];
+ try {
+ jobs = JSON.parse(socket.data);
+ } catch (e) { }
+ newFiles = _cloneDeep(self.files);
+ jobs.map(function (j) {
+ //check if not in completed state
+ let fullPath = j['package-path'];
+ let path = fullPath.split('/');
+ let fileName = path.pop();
+ path = path.join('/');
+ let index = _findIndex(self.files.data[path], function (o) {
+ return fullPath == o.name
+ });
+ if ((index > -1) && newFiles.data[path][index]) {
+ newFiles.data[path][index].status = j.status
} else {
- if(j.status.toUpperCase() == 'LOADING...' || j.status.toUpperCase() == 'IN_PROGRESS') {
- newFiles.data[path].push({
- status: j.status,
- name: fullPath
- })
- } else {
- // if ()
- }
+ if (j.status.toUpperCase() == 'LOADING...' || j.status.toUpperCase() == 'IN_PROGRESS') {
+ newFiles.data[path].push({
+ status: j.status,
+ name: fullPath
+ })
+ } else {
+ // if ()
+ }
}
- })
- self.setState({
- files: newFiles
- })
- // console.log(JSON.parse(socket.data));
- }
+ })
+ self.setState({
+ files: newFiles
+ })
+ // console.log(JSON.parse(socket.data));
+ }
}
this.setState({
jobSocketId: id,
let self = this;
let ws = window.multiplexer.channel(id);
ws.onmessage = (socket) => {
- if (self.fileMonitoringSocketID) {
- let data = [];
- try {
- data = JSON.parse(socket.data);
- } catch(e) {}
- self.getFilelistSuccess(data)
- }
+ if (self.fileMonitoringSocketID) {
+ let data = [];
+ try {
+ data = JSON.parse(socket.data);
+ } catch (e) { }
+ self.getFilelistSuccess(data)
+ }
}
this.setState({
filesState: [],
files: {
- id:[],
- data:{}
+ id: [],
+ data: {}
},
fileMonitoringSocketID: id,
fileMonitoringSocket: ws
}
closeFileManagerSockets() {
- this.fileMonitoringSocketID = null;
- this.setState({
- jobSocketId : null,
- fileMonitoringSocketID : null
- // jobSocket : null,
- // fileMonitoringSocket : null,
- });
+ this.fileMonitoringSocketID = null;
+ this.setState({
+ jobSocketId: null,
+ fileMonitoringSocketID: null
+ // jobSocket : null,
+ // fileMonitoringSocket : null,
+ });
this.jobSocket && this.jobSocket.close();
this.fileMonitoringSocket && this.fileMonitoringSocket.close();
- console.log('closing');
+ console.log('closing');
}
openFileManagerSockets(i) {
let self = this;
let item = i || self.item;
- // this.closeFileManagerSockets();
- this.getInstance().openFileMonitoringSocket(item.id, item.uiState.type).then(function() {
- // // self.getInstance().openDownloadMonitoringSocket(item.id);
+ // this.closeFileManagerSockets();
+ this.getInstance().openFileMonitoringSocket(item.id, item.uiState.type).then(function () {
+ // // self.getInstance().openDownloadMonitoringSocket(item.id);
});
- this.getInstance().openDownloadMonitoringSocket(item.id);
+ this.getInstance().openDownloadMonitoringSocket(item.id);
}
endWatchingJob(id) {
}
deletePackageFile(asset) {
- let {assetType, path} = asset;
+ let {
+ assetType,
+ path
+ } = asset;
let id = this.item.id;
let type = this.item.uiState.type;
this.getInstance().deleteFile(id, type, assetType, path);
deleteFileSuccess = (data) => {
let name = null;
let path = null;
- if (data.assetFolder === 'readme'){
+ if (data.assetFolder === 'readme') {
// freak'n root folder is special
name = data.path;
path = ['.'];
}
let files = _cloneDeep(this.files);
let filesForPath = files.data[path.join('/')]
- _remove(filesForPath, function(c) {
+ _remove(filesForPath, function (c) {
return c.name == name;
});
}
}
-export default alt.createStore(ComposerAppStore, 'ComposerAppStore');
+export default alt.createStore(ComposerAppStore, 'ComposerAppStore');
\ No newline at end of file
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
font-size: 11px;
}
}
-}
\ No newline at end of file
+}
min-width: 300px;
overflow: hidden;
z-index: 1;
+
.CanvasPanelHeader {
h1 {
margin: 0;
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
right: 0;
height: 25px;
min-width: 300px;
- background-color: white;
+ /* background-color: white;*/
+ background: $panel-bg-color;
&.-with-transitions {
transition: height 300ms cubic-bezier(0.230, 1.000, 0.320, 1.000);
}
}
}
+
+ &-buttons {
+ display: -ms-flexbox;
+ display: flex;
+ margin-top: 2px;
+ button {
+ padding: 6px 34px;
+ }
+ }
+ .tray-body {
+ top:31px;
+ }
+ .ConfigParameterMap {
+ padding: 10px 20px;
+ background: $panel-bg-color;
+
+ .toggleable {
+ display:-ms-flexbox;
+ display:flex;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ background: $panel-bg-color-contrast;
+ margin:8px 0;
+ & > .leaf-property {
+ -ms-flex: 1 0 100%;
+ flex: 1 0 100%;
+ margin: 8px 0;
+ padding: 0 8px;
+ background: none;
+ .property-label {
+ }
+ }
+ & > .container-property {
+ -ms-flex: 1 1;
+ flex: 1 1;
+ }
+ }
+ }
+
}
--- /dev/null
+.CatalogItemDetailsEditor {
+ position: absolute;
+ overflow: hidden;
+ &:hover {
+ overflow: auto;
+ }
+ -ms-overflow-style: -ms-autohiding-scrollbar;
+ bottom: 0;
+ top: 0;
+ left: 0;
+ right: 0;
+}
\ No newline at end of file
}
&.-is-deleted {
- opacity: 0.25;
+ display: none;
}
.-is-modified-indicator {
-
/*
*
* Copyright 2016 RIFT.IO Inc
* limitations under the License.
*
*/
+
@import 'main';
@import 'variables';
.DetailsPanel {
@extend .panel;
- border-top: 1px solid mix($panel-border-color, white, 80%);
+ //border-top: 1px solid mix($panel-border-color, white, 80%);
//border-left: 1px solid $panel-border-color;
background-color: ($panel-bg-color-contrast);
position: absolute;
width: 300px;
min-width: 6px;
z-index: 2;
- .DetailsPanelBody {
- @extend .panel-body;
- position: absolute;
- overflow: hidden;
- &:hover {
- overflow: auto;
- }
- -ms-overflow-style: -ms-autohiding-scrollbar;
- bottom: 0;
- top: 0;
- left: 0;
- right: 0;
- min-width: 200px;
- }
+ overflow: visible
}
+
+.DetailsPanelBody {
+ overflow: hidden;
+ >div {
+ position: relative;
+ height: 100%;
+ width: 100%;
+ }
+ min-width: 200px;
+}
\ No newline at end of file
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+@import 'main';
+@import 'variables';
+.DetailsPanelErrors {
+ overflow: hidden;
+ max-height: 300px;
+ position: relative;
+ height: auto;
+ width: 100%;
+ color: red;
+}
\ No newline at end of file
--- /dev/null
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+@import '_variables';
+.DetailsPanelToolbar {
+ display: block;
+ padding: 0;
+ margin: 32px 0 0 0;
+ height: auto;
+ border-top: 1px solid mix($panel-border-color, white, 80%);
+ border-bottom: 0 solid $panel-border-color-light;
+ white-space: nowrap;
+ text-align: center;
+ h1 {
+ white-space: nowrap;
+ text-align: left;
+ font-size: 14px;
+ line-height: 16px;
+ padding: 20px;
+ text-transform: uppercase;
+ font-weight: bold;
+ background-color: #ffffff;
+ }
+ .btn-bar {
+ padding: 0;
+ background-color: rgba(203, 209, 209, 1);
+ .btn-group {
+ padding: 0 8px;
+ display: inline-block;
+ input {
+ margin: 0 4px;
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+@import "main";
+@import "variables";
+@import "ColorGroups";
+
+.config-parameter-map {
+
+ $field-border-radius: 3px;
+ $field-background-color: white;
+ $child-indent-left-right-margin: 5px;
+
+ /* font-size: smaller;*/
+ display:-ms-flexbox;
+ display:flex;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+
+
+
+ max-width: 980px;
+ .config-parameter-titles {
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex: 1 1 100%;
+ flex: 1 1 100%;
+ padding-bottom:10px;
+ color: #AEAEAE;
+ text-transform:uppercase;
+ .config-parameter {
+ padding-left: 10px;
+ }
+ }
+ .config-parameter {
+ display:-ms-flexbox;
+ display:flex;
+ -ms-flex: 1 0 50%;
+ flex: 1 0 50%;
+ &-source, &-request {
+ -ms-flex: 1 1 50%;
+ flex: 1 1 50%;
+ border-top:1px solid silver;
+ padding: 10px 10px;
+ }
+ &-request{
+ line-height: 25px;
+ }
+ &-group {
+ -ms-flex: 1 1 100%;
+ flex: 1 1 100%;
+ display:-ms-flexbox;
+ display:flex;
+ &:nth-child(even) {
+ background: $panel-bg-color-contrast;
+ }
+ }
+ }
+
+
+ h1 {
+ text-align: left;
+ span:last-child {
+ i {
+ display: none;
+ }
+ }
+ }
+
+ h2 {
+ @extend h1;
+
+ }
+
+ .basic-properties-group {
+ > h1 {
+ display: none;
+ }
+ }
+
+ .advanced-properties-group {
+ }
+
+ /* label is used as list item headers */
+ h3 {
+ @extend h2;
+ display: inline-block;
+ .name {
+ color: #586e75;
+ }
+ .value {
+ display: none;
+ color: #002b36;
+ }
+ .info {
+ margin: 4px;
+ }
+ }
+
+ val {
+ display: inline-block;
+ color: #073642;
+ }
+
+ a {
+ &.simple-list-item {
+ display: inline-block;
+ width: 230px;
+ padding: 4px 16px 4px 4px;
+ white-space: nowrap;
+ }
+ &.vld-list-item,
+ &.internal-vld-list-item{
+ border: 1px solid $vld-primary-color;
+ border-radius: 24px;
+ background: white linear-gradient(to right, $vld-primary-color 34px, white 34px);;
+ }
+ &.vnfd-list-item,
+ &.constituent-vnfd-list-item{
+ border: 1px solid $vnfd-primary-color;
+ border-radius: 11px;
+ background: white linear-gradient(to right, $vnfd-primary-color 34px, white 34px);;
+ }
+ &.vdu-list-item {
+ border: 1px solid $vdu-primary-color;
+ border-radius: 11px;
+ background: white linear-gradient(to right, $vdu-primary-color 34px, white 34px);;
+ }
+ &.vnffgd-list-item {
+ border: 1px solid $vnffgd-primary-color;
+ border-radius: 11px;
+ background: white linear-gradient(to right, $vnffgd-primary-color 34px, white 34px);;
+ }
+ }
+
+ &.-is-tree-view {
+ .property {
+
+ position: relative;
+ overflow: hidden;
+
+ margin: 8px 8px;
+
+ background-color: rgba(147, 161, 161, 0.5);
+ border-radius: $field-border-radius;
+
+ > h3 {
+ position: absolute;
+ top: 2px;
+ right: 18px;
+ height: 21px;
+ pointer-events: none;
+ border-radius: $field-border-radius;
+ padding: 3px 0;
+ }
+
+ > val {
+ width: 100%;
+ > .property-content {
+ width: 100%;
+ }
+
+ }
+
+ &.-is-focused {
+ > h3 {
+ /*z-index: -1;*/
+ }
+ }
+
+ &.leaf-property {
+ overflow: hidden;
+ min-height: 25px;
+ > h3 {
+ background: linear-gradient(to right, transparent, $field-background-color 21px);
+ padding-left: 25px;
+ }
+ > val {
+ border-radius: $field-border-radius;
+ > .property-content {
+ border-radius: $field-border-radius;
+ }
+ }
+ }
+
+ &.property:not(.leaf-property) {
+
+ padding: 7px $child-indent-left-right-margin 0 $child-indent-left-right-margin;
+
+ &.list-property {
+ > h3 {
+ padding: 4px 8px;
+ }
+ }
+
+ > h3 {
+ right: auto;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 25px;
+ z-index: 1;
+ padding: 8px;
+ /*background: red;*/
+ pointer-events: all;
+ }
+ > val {
+ margin: 28px 0 8px 0;
+ > .property-content {
+ position: relative;
+ border-radius: $field-border-radius;
+ margin-top: 4px;
+ padding: 4px;
+ &:first-of-type {
+ margin-top: 0;
+ }
+ &.simple-list {
+ min-height: 15px;
+ margin: 11px;
+ .simple-list-item {
+ img,
+ span {
+ margin: 0 5px;
+ vertical-align: middle;
+ }
+ }
+ }
+ }
+ > .property-content:not(.simple-list) {
+ &:nth-of-type(odd) {
+ background-color: rgba(238, 232, 213, 0.33);
+ }
+ &:nth-of-type(even) {
+ background-color: rgba(147, 161, 161, 0.33);
+ }
+ }
+ .tip {
+ font-style: italic;
+ font-size: small;
+ color: #93a1a1;
+ }
+ }
+ }
+
+ .actions {
+ span {
+ vertical-align: middle;
+ padding: 0 5px;
+ }
+ }
+
+ }
+
+ }
+
+ .description {
+ display: none;
+ }
+
+ input,
+ select,
+ textarea {
+ height: 25px;
+ line-height: 25px;
+ max-width: 100%;
+ min-width: 100%;
+ margin: 0;
+ padding: 0 0px 4px 8px;
+ border: 1px solid $field-background-color;
+ border-radius: $field-border-radius;
+ color: #002b36;
+ background-color: $field-background-color;
+ vertical-align: top;
+ &:focus {
+ color: #002b36;
+ background-color: white !important;
+ }
+ &::-webkit-input-placeholder {
+ color: #eee8d5 !important;
+ }
+
+ &:-moz-placeholder { /* Firefox 18- */
+ color: #eee8d5 !important;
+ }
+
+ &::-moz-placeholder { /* Firefox 19+ */
+ color: #eee8d5 !important;
+ }
+
+ &:-ms-input-placeholder {
+ color: #eee8d5 !important;
+ }
+ }
+
+ select {
+ padding-right: 0;
+ margin-right: 0;
+ -webkit-appearance: none;
+ -webkit-border-radius: $field-border-radius;
+ &.-value-not-set {
+ color: #eee8d5;
+ }
+ }
+
+ select {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none; /* using -prefix-free http://leaverou.github.io/prefixfree/*/
+ background: $field-background-color url(../../../node_modules/open-iconic/svg/caret-bottom.svg) no-repeat right 8px center;
+ background-size: 10px;
+ border: {
+ color: $field-background-color;
+ radius: $field-border-radius;
+ style: solid;
+ width: 1px;
+ }
+ }
+
+ /* Removes default arrow for IE10+*/
+ /* IE 8/9 get dafault arrow which covers caret image*/
+ /* as long as caret image is small than and positioned*/
+ /* behind default arrow*/
+ select::-ms-expand {
+ display: none;
+ }
+
+ textarea {
+ height: 50px;
+ }
+
+ input {
+ padding: 0 20px 0 8px;
+ line-height: 25px;
+ }
+
+ input[name$="id"],
+ input.-is-guid {
+ font-size: 10px;
+ font-family: monospace;
+ }
+
+}
@import "main";
@import "ColorGroups";
-
.EditDescriptorModelProperties {
-
$field-border-radius: 3px;
$field-background-color: white;
$child-indent-left-right-margin: 5px;
-
font-size: smaller;
-
h1 {
text-align: left;
span:last-child {
}
}
}
-
h2 {
@extend h1;
-
}
-
.basic-properties-group {
- > h1 {
+ >h1 {
display: none;
}
}
-
- .advanced-properties-group {
- }
-
+ .advanced-properties-group {}
/* label is used as list item headers */
h3 {
@extend h2;
margin: 4px;
}
}
-
val {
display: inline-block;
color: #073642;
}
-
a {
&.simple-list-item {
display: inline-block;
white-space: nowrap;
}
&.vld-list-item,
- &.internal-vld-list-item{
+ &.internal-vld-list-item {
border: 1px solid $vld-primary-color;
border-radius: 24px;
- background: white linear-gradient(to right, $vld-primary-color 34px, white 34px);;
+ background: white linear-gradient(to right, $vld-primary-color 34px, white 34px);
+ ;
}
&.vnfd-list-item,
- &.constituent-vnfd-list-item{
+ &.constituent-vnfd-list-item {
border: 1px solid $vnfd-primary-color;
border-radius: 11px;
- background: white linear-gradient(to right, $vnfd-primary-color 34px, white 34px);;
+ background: white linear-gradient(to right, $vnfd-primary-color 34px, white 34px);
+ ;
}
&.vdu-list-item {
border: 1px solid $vdu-primary-color;
border-radius: 11px;
- background: white linear-gradient(to right, $vdu-primary-color 34px, white 34px);;
+ background: white linear-gradient(to right, $vdu-primary-color 34px, white 34px);
+ ;
}
&.vnffgd-list-item {
border: 1px solid $vnffgd-primary-color;
border-radius: 11px;
- background: white linear-gradient(to right, $vnffgd-primary-color 34px, white 34px);;
+ background: white linear-gradient(to right, $vnffgd-primary-color 34px, white 34px);
+ ;
}
}
-
&.-is-tree-view {
.property {
-
position: relative;
overflow: hidden;
-
margin: 8px 8px;
-
background-color: rgba(147, 161, 161, 0.5);
border-radius: $field-border-radius;
-
- > h3 {
+ >h3 {
position: absolute;
top: 2px;
right: 18px;
border-radius: $field-border-radius;
padding: 3px 0;
}
-
- > val {
+ >val {
width: 100%;
- > .property-content {
+ >.property-content {
width: 100%;
}
-
}
-
&.-is-focused {
- > h3 {
+ >h3 {
//z-index: -1;
}
}
-
&.leaf-property {
overflow: hidden;
min-height: 25px;
- > h3 {
+ >h3 {
background: linear-gradient(to right, transparent, $field-background-color 21px);
padding-left: 25px;
}
- > val {
+ >val {
border-radius: $field-border-radius;
- > .property-content {
+ >.property-content {
border-radius: $field-border-radius;
}
}
}
-
&.property:not(.leaf-property) {
-
padding: 7px $child-indent-left-right-margin 0 $child-indent-left-right-margin;
-
+ min-height: 18px;
&.list-property {
- > h3 {
+ >h3 {
padding: 4px 8px;
}
}
-
- > h3 {
+ &.container-property {
+ >h3 {
+ padding: 4px 8px;
+ }
+ }
+ >h3 {
right: auto;
top: 0;
left: 0;
//background: red;
pointer-events: all;
}
- > val {
+ >val {
margin: 28px 0 8px 0;
- > .property-content {
+ >.property-content {
position: relative;
border-radius: $field-border-radius;
margin-top: 4px;
}
}
}
- > .property-content:not(.simple-list) {
+ >.property-content:not(.simple-list) {
&:nth-of-type(odd) {
background-color: rgba(238, 232, 213, 0.33);
}
}
}
}
-
.actions {
span {
vertical-align: middle;
padding: 0 5px;
}
}
-
}
-
}
-
.description {
- display: none;
+ display: inline-block;
+ margin-left: 15px;
+ font-style: italic;
+ font-size: small;
+ color: #323232;
}
-
input,
select,
textarea {
&::-webkit-input-placeholder {
color: #eee8d5 !important;
}
-
- &:-moz-placeholder { /* Firefox 18- */
+ &:-moz-placeholder {
+ /* Firefox 18- */
color: #eee8d5 !important;
}
-
- &::-moz-placeholder { /* Firefox 19+ */
+ &::-moz-placeholder {
+ /* Firefox 19+ */
color: #eee8d5 !important;
}
-
&:-ms-input-placeholder {
color: #eee8d5 !important;
}
}
-
select {
padding-right: 0;
margin-right: 0;
color: #eee8d5;
}
}
-
select {
appearance: none; // using -prefix-free http://leaverou.github.io/prefixfree/
- background: $field-background-color url(../../../node_modules/open-iconic/svg/caret-bottom.svg) no-repeat right 8px center;
+ background: $field-background-color url(../../../node_modules/open-iconic/svg/caret-bottom.svg) no-repeat right 8px center;
background-size: 10px;
border: {
color: $field-background-color;
width: 1px;
}
}
-
// Removes default arrow for IE10+
// IE 8/9 get dafault arrow which covers caret image
// as long as caret image is small than and positioned
select::-ms-expand {
display: none;
}
-
textarea {
height: 50px;
}
-
input {
padding: 0 20px 0 8px;
line-height: 25px;
}
-
input[name$="id"],
input.-is-guid {
font-size: 10px;
font-family: monospace;
}
-
-}
+}
\ No newline at end of file
.sub-menu {
display: none;
position: absolute;
- top: 36px;
+ top: 30px;
left: -4px;
padding: 4px;
background-color: #f1f1f1;
var CompressionPlugin = require("compression-webpack-plugin");
// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html'
var config = {
devtool: 'source-map',
output: {
},
plugins: [
new HtmlWebpackPlugin({
- filename: '../index.html',
- templateContent: '<div id="app"></div>'
+ filename: '../' + htmlFilename,
+ template: frameworkPath + '/plugin-index.html'
})
]
};
+++ /dev/null
-# 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
-)
-
+++ /dev/null
-/*
- * 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;
+++ /dev/null
-{
- "root": "public",
- "name": "Config",
- "dashboard": "./dashboard/dashboard.jsx",
- "order": 1,
- "priority":1,
- "routes": [
- {
- "label": "Configuration Dashboard",
- "route": "accounts",
- "component": "./dashboard/dashboard.jsx",
- "path": "accounts",
- "type": "internal"
- }]
-}
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 15.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- width="1190.55px" height="841.89px" viewBox="0 0 1190.55 841.89" enable-background="new 0 0 1190.55 841.89"
- xml:space="preserve">
-<g>
- <g>
- <path fill="#DD4814" d="M657.621,123.565c-73.488,0-133.063,59.573-133.063,133.062s59.576,133.069,133.063,133.069
- c73.49,0,133.064-59.58,133.064-133.069S731.111,123.565,657.621,123.565z"/>
- <path fill="#FFFFFF" d="M593.043,338.909c-1.128,1.128-2.632,1.75-4.233,1.75l0,0c-1.605,0-3.112-0.624-4.244-1.757
- c-1.129-1.128-1.751-2.632-1.751-4.234c0-1.602,0.622-3.105,1.751-4.235c1.133-1.132,2.64-1.756,4.243-1.756
- c1.601,0,3.104,0.622,4.233,1.751c1.131,1.131,1.753,2.637,1.753,4.24C594.797,336.273,594.174,337.779,593.043,338.909z"/>
- <path fill="#FFFFFF" d="M619.154,249.424c-0.603-1.92-1.47-3.498-2.579-4.69c-1.105-1.193-2.443-2.085-3.978-2.653
- c-1.543-0.574-3.291-0.865-5.194-0.865s-3.636,0.29-5.147,0.863c-1.509,0.569-2.834,1.462-3.939,2.654
- c-1.107,1.19-1.974,2.769-2.578,4.691c-0.606,1.933-0.914,4.299-0.914,7.032v69.735h-12.038V255.39
- c0-3.572,0.484-6.896,1.438-9.88c0.957-2.996,2.463-5.641,4.475-7.863c2.015-2.226,4.586-3.976,7.643-5.203
- c3.05-1.226,6.742-1.848,10.973-1.848c4.229,0,7.949,0.622,11.059,1.847c3.115,1.227,5.717,2.978,7.732,5.204
- c2.01,2.219,3.532,4.863,4.52,7.861c0.985,2.985,1.485,6.311,1.485,9.882V265h-12.038v-8.544
- C620.072,253.733,619.763,251.367,619.154,249.424z"/>
- <path fill="#FFFFFF" d="M669.396,298.309c0,3.579-0.499,6.905-1.483,9.885c-0.985,2.99-2.507,5.634-4.521,7.86
- c-2.016,2.225-4.617,3.976-7.732,5.203c-3.112,1.226-6.833,1.848-11.06,1.848s-7.917-0.622-10.972-1.849
- c-3.055-1.226-5.626-2.976-7.642-5.201c-2.014-2.226-3.52-4.872-4.476-7.864c-0.954-2.983-1.437-6.308-1.437-9.882v-30.425h12.037
- v29.361c0,2.734,0.309,5.099,0.914,7.029c0.603,1.924,1.471,3.502,2.578,4.694c1.106,1.194,2.434,2.087,3.938,2.654
- c1.511,0.573,3.242,0.863,5.147,0.863c1.903,0,3.651-0.291,5.193-0.864c1.533-0.568,2.871-1.461,3.979-2.653
- c1.108-1.194,1.979-2.773,2.579-4.693c0.607-1.94,0.917-4.305,0.917-7.03v-29.361h12.038V298.309z"/>
- <path fill="#FFFFFF" d="M657.387,259.099c0-3.302,2.685-5.989,5.986-5.989c3.305,0,5.995,2.687,5.995,5.989
- c0,3.305-2.69,5.994-5.995,5.994C660.071,265.093,657.387,262.404,657.387,259.099z"/>
- <path fill="#FFFFFF" d="M693.726,174.163c-0.603-1.92-1.472-3.499-2.581-4.693c-1.107-1.193-2.446-2.085-3.979-2.652
- c-1.54-0.573-3.288-0.863-5.193-0.863c-1.903,0-3.635,0.29-5.146,0.862c-1.508,0.569-2.834,1.462-3.941,2.653
- c-1.105,1.193-1.973,2.772-2.575,4.693c-0.606,1.932-0.914,4.298-0.914,7.032v69.734h-12.038v-70.8
- c0-3.571,0.482-6.896,1.438-9.881c0.958-2.996,2.463-5.642,4.477-7.863c2.012-2.224,4.584-3.975,7.643-5.202
- c3.055-1.227,6.744-1.849,10.969-1.849c4.227,0,7.948,0.622,11.06,1.848c3.119,1.228,5.719,2.979,7.731,5.203
- c2.014,2.22,3.535,4.865,4.522,7.86c0.983,2.988,1.483,6.314,1.483,9.885v9.609h-12.04v-8.543
- C694.64,178.467,694.332,176.101,693.726,174.163z"/>
- <path fill="#FFFFFF" d="M743.963,223.048c0,3.577-0.499,6.903-1.482,9.884c-0.986,2.993-2.506,5.638-4.518,7.86
- c-2.018,2.226-4.619,3.976-7.732,5.203c-3.115,1.226-6.837,1.848-11.06,1.848c-4.229,0-7.921-0.622-10.974-1.849
- c-3.057-1.226-5.626-2.976-7.642-5.201c-2.012-2.223-3.518-4.868-4.476-7.863c-0.954-2.989-1.438-6.313-1.438-9.881v-30.426
- h12.038v29.361c0,2.729,0.308,5.095,0.915,7.031c0.603,1.92,1.469,3.499,2.58,4.692c1.106,1.194,2.432,2.087,3.937,2.654
- c1.513,0.573,3.243,0.863,5.146,0.863c1.907,0,3.654-0.291,5.194-0.864c1.534-0.566,2.872-1.459,3.981-2.653
- c1.106-1.19,1.975-2.769,2.577-4.692c0.607-1.936,0.916-4.301,0.916-7.031v-29.361h12.037V223.048z"/>
- </g>
- <g>
- <path d="M360.49,646.299c-2.566,0-5.878-0.32-9.943-0.962c-4.06-0.642-7.482-1.498-10.264-2.565l3.849-24.377
- c2.14,0.641,4.601,1.172,7.377,1.603c2.781,0.427,5.347,0.642,7.698,0.642c10.265,0,17.586-3.157,21.972-9.461
- c4.38-6.311,6.575-15.557,6.575-27.746V419.527h29.83v163.584c0,21.38-4.866,37.257-14.594,47.631
- C393.257,641.112,379.094,646.299,360.49,646.299z"/>
- <path d="M595.598,581.507c-6.847,1.714-15.877,3.528-27.103,5.454c-11.227,1.924-24.217,2.887-38.972,2.887
- c-12.83,0-23.63-1.875-32.396-5.614c-8.771-3.738-15.826-9.03-21.168-15.876c-5.348-6.842-9.197-14.916-11.547-24.218
- c-2.356-9.302-3.528-19.616-3.528-30.952v-93.66h29.83v87.245c0,20.317,3.208,34.856,9.622,43.622
- c6.416,8.771,17.21,13.15,32.396,13.15c3.208,0,6.52-0.105,9.943-0.32c3.417-0.211,6.625-0.481,9.623-0.802
- c2.992-0.321,5.718-0.642,8.179-0.963c2.457-0.32,4.22-0.691,5.293-1.122V419.527h29.83V581.507z"/>
- <path d="M615.162,646.299c-2.566,0-5.879-0.32-9.943-0.962c-4.06-0.642-7.484-1.498-10.265-2.565l3.85-24.377
- c2.14,0.641,4.601,1.172,7.377,1.603c2.782,0.427,5.348,0.642,7.698,0.642c10.265,0,17.587-3.157,21.972-9.461
- c4.38-6.311,6.575-15.557,6.575-27.746V419.527h29.83v163.584c0,21.38-4.866,37.257-14.595,47.631
- C647.929,641.112,633.766,646.299,615.162,646.299z"/>
- <path d="M850.27,581.507c-6.846,1.714-15.878,3.528-27.104,5.454c-11.227,1.924-24.217,2.887-38.972,2.887
- c-12.83,0-23.63-1.875-32.396-5.614c-8.771-3.738-15.827-9.03-21.17-15.876c-5.347-6.842-9.196-14.916-11.547-24.218
- c-2.355-9.302-3.528-19.616-3.528-30.952v-93.66h29.83v87.245c0,20.317,3.207,34.856,9.622,43.622
- c6.415,8.771,17.211,13.15,32.396,13.15c3.207,0,6.521-0.105,9.942-0.32c3.419-0.211,6.627-0.481,9.624-0.802
- c2.991-0.321,5.718-0.642,8.179-0.963c2.455-0.32,4.22-0.691,5.292-1.122V419.527h29.83V581.507z"/>
- </g>
-</g>
-</svg>
+++ /dev/null
-{
- "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",
- "compression-webpack-plugin": "^0.3.2",
- "cors": "^2.7.1",
- "css-loader": "^0.23.1",
- "file-loader": "^0.8.5",
- "html-webpack-plugin": "^2.9.0",
- "http-proxy": "^1.12.0",
- "loaders.css": "^0.1.2",
- "node-sass": "^3.4.2",
- "react-addons-css-transition-group": "^0.14.7",
- "sass-loader": "^3.1.2",
- "style-loader": "^0.13.0",
- "webpack": "^1.3.0",
- "webpack-dev-server": "^1.10.1"
- }
-}
+++ /dev/null
-/*
- * STANDARD_RIFT_IO_COPYRIGHT
- */
-
-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);
-
-module.exports = app;
+++ /dev/null
-#!/bin/bash
-
-# STANDARD_RIFT_IO_COPYRIGHT
-
-PLUGIN_NAME=accounts
-# 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 --optimize-minimize --progress --config webpack.production.config.js
-echo 'Packaging '$PLUGIN_NAME' using webpack... done'
-echo 'Building plugin '$PLUGIN_NAME'... done'
+++ /dev/null
-#!/bin/bash
-
-# STANDARD_RIFT_IO_COPYRIGHT
-
-plugin=config
-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/.
+++ /dev/null
-/*
- * 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);
-});
+++ /dev/null
-/*
- * STANDARD_RIFT_IO_COPYRIGHT
- */
-@import "style/_colors.scss";
-.Config {
- -ms-flex: 1 1 100%;
- flex: 1 1 100%;
-
- .refreshStatus {
- margin-left: 0.5rem;
- padding: 1rem 0;
-
- .currentStatus {
- text-transform: uppercase;
- display:-ms-flexbox;
- display:flex;
- -ms-flex-line-pack:center;
- align-content:center;
- }
- span.oi {
- cursor: pointer;
- }
- }
- .delete, .cancel {
- cursor: pointer;
- }
- .associateSdnAccount {
- margin: 1rem;
- }
- .create > .flex-row > li {
- -ms-flex: 1 1 auto;
- flex: 1 1 auto;
- margin: 0 .25rem;
- }
- .create > .flex-row > li:first-child {
- margin-left: .5rem;
- }
- .create > .flex-row > li:last-child {
- margin-right: .5rem;
- }
- /* .flex-row > li h3 {*/
- /* background-color: #ffffff;*/
- /* padding: 12px 18px;*/
- /* text-transform: uppercase;*/
- /* }*/
- .create .options {
- -ms-flex-wrap: wrap;
- flex-wrap: wrap;
- text-align: center;
- margin: 24px auto;
- width: 90%;
- flex-wrap: wrap;
- margin: 24px auto;
- width: 90%;
- -ms-flex-pack: start;
- justify-content: flex-start;
- }
- .create .options a {
- background-color: #ffffff;
- -ms-flex: 0 1 32%;
- flex: 0 1 32%;
- margin: 0 4px 4px 0;
- padding: 4px;
- box-shadow: 2px 2px rgba(0, 0, 0, 0.15);
- }
- .create .options a h5 {
- font-size: 10px;
- margin: 5px 0 0 5px;
- text-align: left;
- }
- .create .options a img {
- margin-top: 10px;
- width: 70%;
- }
- .list-pools {
- padding: 24px;
- }
- .list-pools li a {
- background-color: #ffffff;
- display: block;
- font-size: 12px;
- margin-bottom: 24px;
- padding: 12px;
- text-align: center;
- box-shadow: 2px 2px rgba(0, 0, 0, 0.15);
- }
- .list-pools li h4 {
- text-align: left;
- }
- .list-pools li img {
- margin-top: 24px;
- width: 65%;
- }
- .form-actions {
- clear: both;
- margin-top: 36px;
- text-align: center;
- margin-bottom:1rem;
- }
- .form-actions a {
- color: #000000;
- display: inline-block;
- font-size: 12px;
- padding: 8px 64px;
- text-decoration: none;
- text-transform: uppercase;
- box-shadow: 2px 2px rgba(0, 0, 0, 0.15);
- }
- .form-actions a.save {
- background-color: #ffffff;
- border: 1px solid #cccccc;
- border-top: 0;
- cursor: pointer;
- margin-right: 48px;
- }
- .form-actions a.launch {
- background-color: #333333;
- border: 1px solid #000000;
- border-top: 0;
- color: #ffffff;
- }
- .form-actions a.launch:hover, .form-actions a.launch:active {
- background: #00acee;
- color: #ffffff;
- }
- .create .select-type {
- margin: 0.5rem;
- }
- /* .create-fleet-pool label {*/
- /* padding: 0.125rem;*/
- /* display: block;*/
- /* }*/
- /* .create-fleet-pool input {*/
- /* width: 350px;*/
- /* height: 35px;*/
- /* margin: 0px 20px 10px 15px;*/
- /* box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff;*/
- /* font-size: 20px;*/
- /* }*/
- .create .optional {
- font-style: italic;
- }
- .name-input input {
- background:white !important;
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff;
- display: block;
- font-size: 120%;
- height: 35px;
- margin: 0;
- margin-top: 0.25rem;
- width: 350px;
- }
- .select-type {
- display:-ms-flexbox;
- display:flex;
- }
- .accountSelection {
- display:-ms-flexbox;
- display:flex;
- -ms-flex:0 1;
- flex:0 1;
- position:relative;
- -ms-flex-direction:column;
- flex-direction:column;
- margin-right:0.5rem;
- -ms-flex-align:center;
- align-items:center;
- border-top:$gray-light 1px solid;
- border-left:$gray-light 1px solid;
- border-right:$gray-dark 1px solid;
- border-bottom:$gray-dark 1px solid;
- box-shadow: $gray 2px 2px 3px;
- cursor:pointer;
- background:white;
- &:last-child {
- margin-right:0;
- }
-
- input[type="radio"] {
- opacity: 0.01;
- position:absolute;
- top:0;
- }
- &-imageWrapper {
- display:-ms-flexbox;
- display:flex;
- -ms-flex-direction:column;
- flex-direction:column;
- margin:0.5rem;
- padding:0.25rem;
- background:white;
- width:100px;
- height:50px;
- -ms-flex-align:center;
- align-items:center;
- -ms-flex-pack:center;
- justify-content:center;
- img{
- max-width:100px;
- max-height:50px;
- }
- }
- span {
- padding-bottom:0.5rem;
- }
- &-overlay {
- position:absolute;
- top:0;
- left:0;
- right:0;
- bottom:0;
- height:100%;
- width:100%;
- background: rgba(0, 0, 0, 0.25);
- opacity: 0.2;
- }
- &--isSelected{
- border-bottom:$gray-light 1px solid;
- border-right:$gray-light 1px solid;
- border-top:$gray-dark 1px solid;
- border-left:$gray-dark 1px solid;
- box-shadow: $gray 2px 2px 3px inset;
- }
- }
- .configForm {
- margin:0.5rem 0;
- width:100%;
- background: $gray-lighter;
- &-title {
- background-color: #ffffff;
- padding: 12px 0.5rem;
- text-transform: uppercase;
- &--edit {
- display:-ms-flexbox;
- display:flex;
- -ms-flex-pack:justify;
- justify-content:space-between;
- -ms-flex-align: center;
- align-items: center;
- >div {
- display:-ms-flexbox;
- display:flex;
- -ms-flex-align:center;
- align-items:center;
- }
- }
- }
- img {
- height:2rem;
- margin:0 0.5rem;
- }
- &-content {
- padding:0.5rem;
- }
- &-nestedParams {
- display:-ms-flexbox;
- display:flex;
- > label {
- margin-right:0.5rem;
- }
- > label:last-child {
- margin-right:0rem;
- }
-
- }
- }
- .row {
- display:-ms-flexbox;
- display:flex;
- }
-}
+++ /dev/null
-/*
- * STANDARD_RIFT_IO_COPYRIGHT
- */
-module.exports = function(Alt) {
- return Alt.generateActions(
- 'getResourceOrchestratorSuccess',
- 'updateResourceOrchestratorSuccess'
- );
-}
+++ /dev/null
-/*
- * STANDARD_RIFT_IO_COPYRIGHT
- */
-import $ from 'jquery';
-var Utils = require('utils/utils.js');
-let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
-let HOST = API_SERVER;
-let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
-let DEV_MODE = require('utils/rw.js').getSearchParams(window.location).dev_mode || false;
-
-if (DEV_MODE) {
- HOST = window.location.protocol + '//' + window.location.hostname;
-}
-
-
-module.exports = function(Alt) {
- return {
- getResourceOrchestrator: {
- remote: function() {
- return new Promise(function(resolve, reject) {
- $.ajax({
- url: 'passthrough/data/api/running/resource-orchestrator' + '?api_server=' + API_SERVER,
- type: 'GET',
- beforeSend: Utils.addAuthorizationStub,
- contentType: "application/json",
- success: function(data) {
- resolve(data["rw-launchpad:resource-orchestrator"]);
- },
- error: function(error) {
- console.log("There was an error updating the account: ", arguments);
-
- }
- }).fail(function(xhr){
- //Authentication and the handling of fail states should be wrapped up into a connection class.
- Utils.checkAuthentication(xhr.status);
- return reject('error');
- });
- });
- },
- interceptResponse: interceptResponse({
- 'error': 'There was an error retrieving the resource orchestrator information.'
- }),
- success: Alt.actions.global.getResourceOrchestratorSuccess,
- loading: Alt.actions.global.showScreenLoader,
- error: Alt.actions.global.showNotification
- },
- update: {
- remote: function(state, account) {
-
- return new Promise(function(resolve, reject) {
- $.ajax({
- url: 'resource-orchestrator' + '?api_server=' + API_SERVER,
- type:'PUT',
- beforeSend: Utils.addAuthorizationStub,
- data: JSON.stringify(account),
- contentType: "application/json",
- success: function(data) {
- resolve({data});
- },
- error: function(error) {
- console.log("There was an error updating the account: ", arguments);
- return null;
- }
- }).fail(function(xhr){
- //Authentication and the handling of fail states should be wrapped up into a connection class.
- Utils.checkAuthentication(xhr.status);
- return reject('error');
- });
-
- });
- },
- interceptResponse: interceptResponse({
- 'error': 'There was an error updating the account.'
- }),
- success: Alt.actions.global.updateResourceOrchestratorSuccess,
- 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;
- }
- }
-}
+++ /dev/null
-/*
- * STANDARD_RIFT_IO_COPYRIGHT
- */
-import AccountActions from './configActions.js';
-import AccountSource from './configSource.js';
-
-let tempData = {
- "rw-launchpad:resource-orchestrator": {
- "name": "test",
- "account-type": "openmano",
- "openmano": {
- "port": 9090,
- "tenant-id": "d2581f60-4f28-11e6-9732-fa163e4bfd3e",
- "host": "10.0.55.39"
- }
- }
-}
-
-var rw = require('utils/rw.js');
-var altImage = rw.getSearchParams(window.location).alt_image;
-
-let Params = {
- //Config Agent
- ConfigAgent: {
-
- }
-}
-
-
-
-let AccountMeta = {
- 'account-types': ['openmano', 'rift-ro'],
- 'rift-ro' : [],
- 'openmano' : [{
- label: "Host",
- ref: 'host'
- }, {
- label: "Port",
- ref: 'port'
- }, {
- label: "Tenant ID",
- ref: 'tenant-id'
- }],
- imageByType: {
- "openmano": altImage || require("../../images/openmano.png"),
- "rift-ro": require("../../images/riftio.png")
-
- },
- labelByType: {
- "openmano": "OpenStack",
- "rift-ro": "Cloudsim"
- }
-}
-
-export default class ConfigStore {
- constructor() {
- this.account = {};
- this.accountType = 'openmano';
- this.refreshingAll = false;
- this.sdnOptions = [];
- this.AccountMeta = AccountMeta;
- this.bindActions(AccountActions(this.alt));
- this.registerAsync(AccountSource);
- this.exportPublicMethods({
- getROAccount: this.getROAccount,
- handleParamChange: this.handleParamChange,
- handleNameChange: this.handleNameChange,
- handleAccountTypeChange: this.handleAccountTypeChange,
- updateAccount: this.updateAccount,
- getImage: this.getImage
- })
- }
- setAccountTemplate = (AccountType) => {
- let state = {};
- let account = {
- name: '',
- 'account-type': AccountType || 'rift-ro',
- };
- account[AccountType || 'rift-ro'] = {};
- state.account = account;
- state.accountType = AccountType;
- if (AccountType == this.initialAccountType) {
- state.account = this.initialAccount;
- }
- this.setState(state)
- }
- updateAccount = (account) => {
- this.setState({account:account})
- }
- getROAccount = () => {
- let data = tempData["rw-launchpad:resource-orchestrator"]
- this.setState({
- account: data
- })
- }
- handleNameChange = (event) => {
- var account = this.account;
- account.name = event.target.value;
- this.setState(
- {
- account:account
- }
- );
- }
- handleAccountTypeChange = (event) => {
- var accountType = event.target.value;
- this.setAccountTemplate(accountType);
- }
- handleParamChange(node, event) {
- return function(event) {
- var account = this.state.account;
- account[account['account-type']][node.ref] = event.target.value;
- this.updateAccount(account);
- }.bind(this);
- }
- getImage = (type) => {
- return AccountMeta.imageByType[type];
- }
- getResourceOrchestratorSuccess = (data) => {
- this.alt.actions.global.hideScreenLoader.defer();
- if(!data) {
- this.setAccountTemplate(false)
- } else {
- this.setState({
- initialAccount: data,
- initialAccountType: data['account-type'],
- account: data,
- accountType: data['account-type'] || 'rift-ro'
- });
- }
- }
- updateResourceOrchestratorSuccess = (data) => {
- this.alt.actions.global.showNotification.defer({type:'success', msg: 'Resource Orchestrator has been succesfully updated'});
- }
-}
+++ /dev/null
-/*
- * STANDARD_RIFT_IO_COPYRIGHT
- */
-
-import React from 'react';
-import AppHeader from 'widgets/header/header.jsx';
-import ConfigStore from './configStore.js';
-import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
-import 'style/layout.scss';
-import './config.scss';
-import {Panel, PanelWrapper} from 'widgets/panel/panel';
-import TextInput from 'widgets/form_controls/textInput.jsx';
-import Button from 'widgets/button/rw.button.js';
-
-class ConfigDashboard extends React.Component {
- constructor(props) {
- super(props);
- this.Store = this.props.flux.stores.hasOwnProperty('ConfigStore') ? this.props.flux.stores.ConfigStore : this.props.flux.createStore(ConfigStore, "ConfigStore");
- this.state = this.Store.getState();
- }
- componentWillMount() {
- this.Store.listen(this.updateState);
- this.Store.getResourceOrchestrator();
- }
- componentWillUnmount() {
- this.Store.unlisten(this.updateState);
- }
- updateState = (state) => {
- this.setState(state);
- }
- updateAccount = (e) => {
- e.preventDefault();
- e.stopPropagation();
- this.Store.update(this.state.account);
- }
- render() {
- let self = this;
- let html;
- let Account = this.state.account;
- let AccountMeta = this.state.AccountMeta;
- let AccountType = this.state.accountType;
- let isEdit = true;
- let ParamsHTML = null;
- let Store = this.Store;
- let Types = this.state.AccountMeta['account-types'];
-
- let selectAccountStack = [];
- let selectAccountHTML = null;
-
-
- if (Account['account-type']) {
- for (var i = 0; i < Types.length; i++) {
- var node = Types[i];
- var isSelected = (Account['account-type'] == node);
- selectAccountStack.push(
- <label key={i} className={"accountSelection " + (isSelected ? "accountSelection--isSelected" : "")}>
- <div className={"accountSelection-overlay" + (isSelected ? "accountSelection-overlay--isSelected" : "")}></div>
- <div className="accountSelection-imageWrapper">
- <img src={Store.getImage(node)}/>
- </div>
- <input type="radio" name="account"
- onChange={Store.handleAccountTypeChange} defaultChecked={node == Types[0]} value={node} />{node}
- </label>
- )
- }
- selectAccountHTML = (
- <Panel className="accountForm" title="Select Account Type" no-corners>
- <div className="select-type accountForm-content row" >
- {selectAccountStack}
- </div>
- </Panel>
- );
- }
-
- if (AccountMeta[AccountType] && AccountMeta[AccountType].length > 0) {
- var paramsStack = [];
- var optionalField = '';
- for (var i = 0; i < AccountMeta[AccountType].length; i++) {
- var node = AccountMeta[AccountType][i];
- var value = ""
- if (Account[AccountType]) {
- value = Account[AccountType][node.ref]
- }
- paramsStack.push(
- <TextInput key={node.label} className="accountForm-input" label={node.label} required={!node.optional} onChange={this.Store.handleParamChange(node)} value={value} />
- );
- }
- ParamsHTML = (
- <Panel className="create-fleet-pool accountForm" title={(isEdit ? 'Update' : 'Enter') + ' Account Details'} no-corners>
- <div className="accountForm-content">
- {paramsStack}
- </div>
- </Panel>
- )
- } else {
- ParamsHTML = (
- <Panel className="create-fleet-pool accountForm" title={(isEdit ? 'Update' : 'Enter') + ' Account Details'} no-corners>
- <label style={{'marginLeft':'17px', color:'#888'}}>No Details Required</label>
- </Panel>
- )
- }
-
-
-
- html = (
- <PanelWrapper className="column Config" style={{'alignContent': 'center', 'flexDirection': 'column'}}>
- <form className="app-body create Accounts" onSubmit={this.preventDefault} onKeyDown={this.evaluateSubmit}>
- <div className="noticeSubText noticeSubText_right">
- * required
- </div>
- <div>
- <Panel className="create-fleet-pool accountForm" title="Resource Orchestrator" no-corners>
- <div className="accountForm-content">
- <TextInput className="accountForm-input" label={"Name"} onChange={this.Store.handleNameChange} value={Account.name} />
- </div>
- </Panel>
-
- {
- selectAccountHTML
- }
- {
- ParamsHTML
- }
- </div>
- <div className="form-actions">
- <Button key="4" role="button" className="update dark" label="Update" onClick={this.updateAccount} />
- </div>
- </form>
- </PanelWrapper>
- );
- return html;
- }
-}
-// onClick={this.Store.update.bind(null, Account)}
-ConfigDashboard.contextTypes = {
- router: React.PropTypes.object
-};
-
-export default SkyquakeComponent(ConfigDashboard);
+++ /dev/null
-/*
- * STANDARD_RIFT_IO_COPYRIGHT
- */
-
-import React from 'react';
-import Button from 'widgets/button/rw.button.js';
-import _cloneDeep from 'lodash/cloneDeep';
-import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
-import Crouton from 'react-crouton';
-import TextInput from 'widgets/form_controls/textInput.jsx';
-import 'style/common.scss';
-import './config.scss';
-class Account extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
- this.state.account = {};
- }
- storeListener = (state) => {
- if(!state.account) {
- this.setUp(this.props)
- }
- state.account && this.setState({account: state.account,accountType: state.accountType, types: state.types, sdnOptions: state.sdnOptions})
- }
- componentWillMount() {
- this.props.store.listen(this.storeListener);
- this.setUp(this.props);
- }
- componentWillReceiveProps(nextProps) {
- if(JSON.stringify(nextProps.params) != JSON.stringify(this.props.params)){
- this.setUp(nextProps);
- }
- }
- componentWillUnmount() {
- this.props.store.unlisten(this.storeListener);
- }
- setUp(props){
- if(props.params.name != 'create') {
- this.props.store.viewAccount({type: props.params.type, name: props.params.name});
- } else {
- this.props.store.setAccountTemplate(props.params.type);
- }
- }
- create(e) {
- e.preventDefault();
- var self = this;
- var Account = this.state.account;
- let AccountType = this.state.accountType;
- if (Account.name == "") {
- self.props.flux.actions.global.showNotification("Please give the account a name");
- return;
- } else {
- var type = Account['account-type'];
- var params = Account.params;
-
- if(params) {
- for (var i = 0; i < params.length; i++) {
- var param = params[i].ref;
- if (typeof(Account[type]) == 'undefined' || typeof(Account[type][param]) == 'undefined' || Account[type][param] == "") {
- if (!params[i].optional) {
- self.props.flux.actions.global.showNotification("Please fill all account details");
- return;
- }
- }
- }
- }
-
- let nestedParams = Account.nestedParams && Account.nestedParams;
- if (nestedParams && nestedParams.params) {
- for (let i = 0; i < nestedParams.params.length; i++) {
- let nestedParam = nestedParams.params[i].ref;
- if (typeof(Account[type]) == 'undefined' || typeof(Account[type][nestedParams['container-name']][nestedParam]) == 'undefined' || Account[type][nestedParams['container-name']][nestedParam] == "") {
- if (!nestedParams.params[i].optional) {
- self.props.flux.actions.global.showNotification("Please fill all account details");
- return;
- }
- }
- }
- }
- }
-
- let newAccount = _cloneDeep(removeTrailingWhitespace(Account));
- delete newAccount.params;
- newAccount.nestedParams &&
- newAccount.nestedParams['container-name'] &&
- delete newAccount[newAccount.nestedParams['container-name']];
- delete newAccount.nestedParams;
-
- this.props.flux.actions.global.showScreenLoader();
- this.props.store.create(newAccount, AccountType).then(function() {
- self.props.router.push({pathname:'accounts'});
- self.props.flux.actions.global.hideScreenLoader.defer();
- },
- function() {
- self.props.flux.actions.global.showNotification("There was an error creating your account. Please contact your system administrator.");
- self.props.flux.actions.global.hideScreenLoader.defer();
- });
- }
- update(e) {
- e.preventDefault();
- var self = this;
- var Account = this.state.account;
- let AccountType = this.state.accountType;
- this.props.flux.actions.global.showScreenLoader();
- this.props.store.update(Account, AccountType).then(function() {
- self.props.router.push({pathname:'accounts'});
- self.props.flux.actions.global.hideScreenLoader();
- },
- function() {
-
- });
- }
- cancel = (e) => {
- e.preventDefault();
- e.stopPropagation();
- this.props.router.push({pathname:'accounts'});
- }
- handleDelete = () => {
- let self = this;
- let msg = 'Preparing to delete "' + self.state.account.name + '"' +
- ' Are you sure you want to delete this ' + self.state.accountType + ' account?"';
- if (window.confirm(msg)) {
- this.props.store.delete(self.state.accountType, self.state.account.name).then(function() {
- self.props.flux.actions.global.hideScreenLoader();
- self.props.router.push({pathname:'accounts'});
- }, function(){
- // self.props.flux.actions.global.hideScreenLoader.defer();
- // console.log('Delete Account Fail');
- });
- } else {
- self.props.flux.actions.global.hideScreenLoader();
- }
- }
- handleNameChange(event) {
- this.props.store.handleNameChange(event);
- }
- handleAccountTypeChange(node, event) {
- this.props.store.handleAccountTypeChange(node, event);
- }
- handleSelectSdnAccount = (e) => {
- var tmp = this.state.account;
- if(e) {
- tmp['sdn-account'] = e;
- } else {
- if(tmp['sdn-account']) {
- delete tmp['sdn-account'];
- }
- }
- console.log(e, tmp)
- }
- preventDefault = (e) => {
- e.preventDefault();
- e.stopPropagation();
- }
- evaluateSubmit = (e) => {
- if (e.keyCode == 13) {
- if (this.props.edit) {
- this.update(e);
- } else {
- this.create(e);
- }
- e.preventDefault();
- e.stopPropagation();
- }
- }
-
- render() {
- let self = this;
- let {store, ...props} = this.props;
- // This section builds elements that only show up on the create page.
- // var name = <label>Name <input type="text" onChange={this.handleNameChange.bind(this)} style={{'textAlign':'left'}} /></label>;
- var name = <TextInput label="Name" onChange={this.handleNameChange.bind(this)} required={true} />;
- let params = null;
- let selectAccount = null;
- let resfreshStatus = null;
- let Account = this.state.account;
- // AccountType is for the view, not the data account-type value;
- let AccountType = this.state.accountType;
- let Types = this.state.types;
- let isEdit = this.props.params.name != 'create';
- var buttons;
- let cloudResources = Account['cloud-resources-state'] && Account['cloud-resources-state'][Account['account-type']];
- let cloudResourcesStateHTML = null;
-
- // Account Type Radio
- var selectAccountStack = [];
- if (!isEdit) {
- buttons = [
- <Button key="0" onClick={this.cancel} className="cancel light" label="Cancel"></Button>,
- <Button key="1" role="button" onClick={this.create.bind(this)} className="save dark" label="Save" />
- ]
- for (var i = 0; i < Types.length; i++) {
- var node = Types[i];
- var isSelected = (Account['account-type'] == node['account-type']);
- selectAccountStack.push(
- <label key={i} className={"accountSelection " + (isSelected ? "accountSelection--isSelected" : "")}>
- <div className={"accountSelection-overlay" + (isSelected ? "accountSelection-overlay--isSelected" : "")}></div>
- <div className="accountSelection-imageWrapper">
- <img src={store.getImage(node['account-type'])}/>
- </div>
- <input type="radio" name="account" onChange={this.handleAccountTypeChange.bind(this, node)} defaultChecked={node.name == Types[0].name} value={node['account-type']} />{node.name}
- </label>
- )
- }
- selectAccount = (
- <div className="accountForm">
- <h3 className="accountForm-title">Select Account Type</h3>
- <div className="select-type accountForm-content" >
- {selectAccountStack}
- </div>
- </div>
- );
- } else {
- selectAccount = null
- }
-
- //
- // This sections builds the parameters for the account details.
- if (Account.params) {
- var paramsStack = [];
- var optionalField = '';
- for (var i = 0; i < Account.params.length; i++) {
- var node = Account.params[i];
- var value = ""
- if (Account[Account['account-type']]) {
- value = Account[Account['account-type']][node.ref]
- }
- if (this.props.edit && Account.params) {
- value = Account.params[node.ref];
- }
- paramsStack.push(
- <TextInput key={node.label} className="accountForm-input" label={node.label} required={!node.optional} onChange={this.props.store.handleParamChange(node)} value={value} />
- );
- }
- params = (
- <li className="create-fleet-pool accountForm">
- <h3 className="accountForm-title"> {isEdit ? 'Update' : 'Enter'} Account Details</h3>
- <div className="accountForm-content">
- {paramsStack}
- </div>
- </li>
- )
- } else {
- params = (
- <li className="create-fleet-pool accountForm">
- <h3 className="accountForm-title"> {isEdit ? 'Update' : 'Enter'}</h3>
- <label style={{'marginLeft':'17px', color:'#888'}}>No Details Required</label>
- </li>
- )
- }
-
- // This section builds elements that only show up in the edit page.
- if (isEdit) {
- name = <label>{Account.name}</label>;
- buttons = [
- <Button key="2" onClick={this.handleDelete} className="light" label="Remove Account" />,
- <Button key="3" onClick={this.cancel} className="light" label="Cancel" />,
- <Button key="4" role="button" onClick={this.update.bind(this)} className="update dark" label="Update" />
- ];
- resfreshStatus = Account['connection-status'] ? (
- <div className="accountForm">
- <div className="accountForm-title accountForm-title--edit">
- Connection Status
- </div>
- <div className="accountForm-content" style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
- <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
- <AccountConnectivityStatus status={Account['connection-status'].status} />
- {Account['connection-status'].status.toUpperCase()}
- </div>
- <Button className="refreshList light" onClick={this.props.store.refreshAccount.bind(this, Account.name, AccountType)} label="REFRESH STATUS"></Button>
- </div>
- {
- Account['connection-status'].status.toUpperCase() === 'FAILURE' ?
- displayFailureMessage(Account['connection-status'].details) : null
- }
- </div>
- ) : null;
- // cloudResourcesStateHTML = (
- // <div className="accountForm">
- // <h3 className="accountForm-title">Resources Status</h3>
- // <div className="accountForm-content" >
- // <ul>
- // {
- // cloudResources && props.AccountMeta.resources[Account['account-type']].map(function(r, i) {
-
- // return (
- // <li key={i}>
- // {r}: {cloudResources[r]}
- // </li>
- // )
- // }) || 'No Additional Resources'
- // }
- // </ul>
- // </div>
- // </div>
- // )
- }
-
- var html = (
-
- <form className="app-body create Accounts" onSubmit={this.preventDefault} onKeyDown={this.evaluateSubmit}>
- <div className="noticeSubText noticeSubText_right">
- * required
- </div>
- <div className="associateSdnAccount accountForm">
- <h3 className="accountForm-title">Account</h3>
- <div className="accountForm-content" style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
- <h4 style={{flex: '1'}}>{name}</h4>
- { isEdit ?
- (
- <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
- <img src={store.getImage(Account['account-type'])}/> {props.AccountMeta.labelByType[Account['account-type']]}
- </div>)
- : null
- }
- </div>
- </div>
-
- {selectAccount}
- {sdnAccounts}
- {resfreshStatus}
- {cloudResourcesStateHTML}
- <ol className="flex-row">
- {params}
- </ol>
- <div className="form-actions">
- {buttons}
- </div>
- </form>
- )
- return html;
- }
-}
-
-function displayFailureMessage(msg) {
- return (
- <div className="accountForm-content" style={{maxWidth: '600px'}}>
- <div style={{paddingBottom: '1rem'}}>Details:</div>
- <div>
- {msg}
- </div>
-
- </div>
- )
-}
-
-class SelectOption extends React.Component {
- constructor(props){
- super(props);
- }
- handleOnChange = (e) => {
- this.props.onChange(JSON.parse(e.target.value));
- }
- render() {
- let html;
- html = (
- <select className={this.props.className} onChange={this.handleOnChange}>
- {
- this.props.options.map(function(op, i) {
- return <option key={i} value={JSON.stringify(op.value)}>{op.label}</option>
- })
- }
- </select>
- );
- return html;
- }
-}
-SelectOption.defaultProps = {
- options: [],
- onChange: function(e) {
- console.dir(e)
- }
-}
-
-function removeTrailingWhitespace(Account) {
- var type = Account['account-type'];
- var params = Account.params;
-
- if(params) {
- for (var i = 0; i < params.length; i++) {
- var param = params[i].ref;
- if(typeof(Account[type][param]) == 'string') {
- Account[type][param] = Account[type][param].trim();
- }
- }
- }
-
- let nestedParams = Account.nestedParams;
- if (nestedParams && nestedParams.params) {
- for (let i = 0; i < nestedParams.params.length; i++) {
- let nestedParam = nestedParams.params[i].ref;
- let nestedParamValue = Account[type][nestedParams['container-name']][nestedParam];
- if (typeof(nestedParamValue) == 'string') {
- Account[type][nestedParams['container-name']][nestedParam] = nestedParamValue.trim();
- }
- }
- }
- return Account;
-}
-
-export default SkyquakeComponent(Account)
+++ /dev/null
-/*
- * 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);
-
-
+++ /dev/null
-/*
- * 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 CompressionPlugin = require("compression-webpack-plugin");
-// 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>'
- })
- ]
-};
-
-if (process.argv.indexOf('--optimize-minimize') !== -1) {
- // we are going to output a gzip file in the production process
- config.output.filename = "gzip-" + config.output.filename;
- config.plugins.push(new webpack.DefinePlugin({ // <-- key to reducing React's size
- 'process.env': {
- 'NODE_ENV': JSON.stringify('production')
- }
- }));
- config.plugins.push(new CompressionPlugin({
- asset: "[path]", // overwrite js file with gz file
- algorithm: "gzip",
- test: /\.(js)$/
- }));
-}
-module.exports = config;
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
url: utils.confdPort(api_server) + APIVersion +'/api/operational/crash?deep',
"root": "public",
"name": "Debug",
"dashboard": "./crash.jsx",
- "order": 100,
+ "order": 4,
"priority":2,
+ "admin_link": true,
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-rbac-platform:platform-admin",
+ "rw-rbac-platform:platform-oper"
+ ],
"routes" : [{
"label": "Debug",
"route": "/",
PLUGIN_NAME=debug
# 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 --optimize-minimize --progress --config webpack.production.config.js
-echo 'Packaging '$PLUGIN_NAME' using webpack... done'
-echo 'Building plugin '$PLUGIN_NAME'... done'
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
#!/bin/bash
-#
+#
# Copyright 2016 RIFT.IO Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
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
+tar -cf $bcache_dir/plugins/$plugin/node_modules.tar node_modules package.json -C $source_dir
#cp -Lrp $source_dir/node_modules $bcache_dir/plugins/$plugin/.
var CompressionPlugin = require("compression-webpack-plugin");
// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
var config = {
devtool: 'source-map',
entry: mainPath,
},
plugins: [
new HtmlWebpackPlugin({
- filename: '../index.html',
- templateContent: '<div id="app"></div>'
+ filename: '../' + htmlFilename,
+ template: frameworkPath + '/plugin-index.html'
})
]
};
PLUGIN_NAME=goodbyeworld
# 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 --optimize-minimize --progress --config webpack.production.config.js
-echo 'Packaging '$PLUGIN_NAME' using webpack... done'
-echo 'Building plugin '$PLUGIN_NAME'... done'
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
var CompressionPlugin = require("compression-webpack-plugin");
// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
var config = {
devtool: 'source-map',
entry: mainPath,
},
plugins: [
new HtmlWebpackPlugin({
- filename: '../index.html',
+ filename: '../' + htmlFilename,
templateContent: '<div id="content"></div>'
})
]
PLUGIN_NAME=helloworld
# 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 --optimize-minimize --progress --config webpack.production.config.js
-echo 'Packaging '$PLUGIN_NAME' using webpack... done'
-echo 'Building plugin '$PLUGIN_NAME'... done'
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
var CompressionPlugin = require("compression-webpack-plugin");
// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
var config = {
devtool: 'source-map',
entry: mainPath,
},
plugins: [
new HtmlWebpackPlugin({
- filename: '../index.html',
+ filename: '../' + htmlFilename,
templateContent: '<div id="content"></div>'
})
]
var NetworkTopology = {};
var VDUR = {};
var CloudAccount = {};
+var ResourceOrchestratorAccount = {};
var ConfigAgentAccount = {};
var RPC = {};
var SSHkey = {};
RPC.executeNSServicePrimitive = function(req) {
var api_server = req.query['api_server'];
return new Promise(function(resolve, reject) {
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operations/exec-ns-service-primitive');
var jsonData = {
- "input": req.body
+ "input": utils.addProjectContextToRPCPayload(req, uri, req.body)
};
var headers = _.extend({},
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}
);
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/operations/exec-ns-service-primitive',
+ url: uri,
method: 'POST',
headers: headers,
forever: constants.FOREVER_ON,
// var nsr_id = req.body['nsr_id_ref'];
// var nsConfigPrimitiveName = req.body['name'];
return new Promise(function(resolve, reject) {
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operations/get-ns-service-primitive-values');
+
var jsonData = {
- "input": req.body
+ "input": utils.addProjectContextToRPCPayload(req, uri, req.body)
};
var headers = _.extend({},
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}
);
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/operations/get-ns-service-primitive-values',
+ uri: uri,
method: 'POST',
headers: headers,
forever: constants.FOREVER_ON,
}
}
jsonData.input[rpcInfo[Type].label] = Name;
+
+ var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operations/' + rpcInfo[Type].rpc);
+
+ jsonData.input = utils.addProjectContextToRPCPayload(req, uri, jsonData.input);
+
var headers = _.extend({},
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}
);
return new Promise(function(resolve, reject) {
-
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/operations/' + rpcInfo[Type].rpc,
+ uri: uri,
method: 'POST',
headers: headers,
forever: constants.FOREVER_ON,
Catalog.get = function(req) {
var api_server = req.query['api_server'];
var results = {}
+ var projectPrefix = req.session.projectId ? "project-" : "";
return new Promise(function(resolve, reject) {
Promise.all([
rp({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep'),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
resolveWithFullResponse: true
}),
rp({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd?deep'),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
resolveWithFullResponse: true
}),
rp({
- uri: utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata?deep'),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
// headers: _.extend({},
// constants.HTTP_HEADERS.accept.collection,
// {
- // 'Authorization': req.get('Authorization')
+ // 'Authorization': req.session && req.session.authorization
// }),
// forever: constants.FOREVER_ON,
// rejectUnauthorized: false,
var vnfdCatalog = null;
var vnfdDict = {};
if (result[1].body) {
- vnfdCatalog = JSON.parse(result[1].body).collection['vnfd:vnfd'].map(function(v, i) {
+ response[1].descriptors = utils.dataToJsonSansPropNameNamespace(result[1].body).collection['vnfd'];
+ vnfdCatalog = response[1].descriptors.map(function(v, i) {
vnfdDict[v.id] = v['short-name'] || v.name;
})
}
if (result[0].body) {
- response[0].descriptors = JSON.parse(result[0].body).collection['nsd:nsd'];
+ response[0].descriptors = utils.dataToJsonSansPropNameNamespace(result[0].body).collection['nsd'];
if (result[2].body) {
var data = JSON.parse(result[2].body);
- if (data && data["nsr:ns-instance-opdata"] && data["nsr:ns-instance-opdata"]["rw-nsr:nsd-ref-count"]) {
- var nsdRefCountCollection = data["nsr:ns-instance-opdata"]["rw-nsr:nsd-ref-count"];
+ if (data && data["ns-instance-opdata"]) {
response[0].descriptors.map(function(nsd) {
if (!nsd["meta"]) {
nsd["meta"] = {};
if (typeof nsd['meta'] == 'string') {
nsd['meta'] = JSON.parse(nsd['meta']);
}
- nsd["meta"]["instance-ref-count"] = _.findWhere(nsdRefCountCollection, {
- "nsd-id-ref": nsd.id
- })["instance-ref-count"];
nsd["constituent-vnfd"] && nsd["constituent-vnfd"].map(function(v) {
v.name = vnfdDict[v["vnfd-id-ref"]];
})
}
}
};
- if (result[1].body) {
- response[1].descriptors = JSON.parse(result[1].body).collection['vnfd:vnfd'];
- };
- // if (result[2].body) {
- // response[2].descriptors = JSON.parse(result[2].body).collection['pnfd:pnfd'];
- // };
resolve({
statusCode: response.statusCode || 200,
data: JSON.stringify(response)
console.log('Deleting', catalogType, id, 'from', api_server);
return new Promise(function(resolve, reject) {
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog/' + catalogType + '/' + id,
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog/' + catalogType + '/' + encodeURIComponent(id)),
method: 'DELETE',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
Catalog.getVNFD = function(req) {
var api_server = req.query['api_server'];
var vnfdID = req.body.data;
- var authorization = req.get('Authorization');
+ var authorization = req.session && req.session.authorization;
var VNFDs = [];
if (typeof(vnfdID) == "object" && vnfdID.constructor.name == "Array") {
vnfdID.map(function(id) {
function requestVNFD(id) {
return new Promise(function(resolve, reject) {
- var url = utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd' + (id ? '/' + id : '') + '?deep';
+ var url = utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd' + (id ? '/' + encodeURIComponent(id) : '') + '?deep';
request({
- uri: url,
+ uri: utils.projectContextUrl(req, url),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
'Authorization': authorization
return new Promise(function(resolve, reject) {
var requestHeaders = {};
_.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog'),
method: 'POST',
headers: requestHeaders,
forever: constants.FOREVER_ON,
return new Promise(function(resolve, reject) {
var requestHeaders = {};
_.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog' + '/' + catalogType + '/' + id,
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/' + catalogType + '-catalog' + '/' + catalogType + '/' + encodeURIComponent(id)),
method: 'PUT',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var nsrPromises = [];
var api_server = req.query["api_server"];
var id = req.params.id;
- var nsdInfo = new Promise(function(resolve, reject) {
+ var projectPrefix = req.session.projectId ? "project-" : "";
+ var vnfdInfo = new Promise(function(resolve, reject) {
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/nsd-catalog/nsd?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/vnfd-catalog/vnfd'),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
}, function(error, response, body) {
- if (utils.validateResponse('NSR.get nsd-catalog', error, response, body, resolve, reject)) {
+ if (utils.validateResponse('NSR.get vnfd-catalog', error, response, body, resolve, reject)) {
var data;
var isString = typeof(response.body) == "string";
if (isString && response.body == '') return resolve('empty');
data = isString ? JSON.parse(response.body) : response.body;
- var nsdData = data.collection["nsd:nsd"];
- if (nsdData.constructor.name == "Object") {
- nsdData = [nsdData];
+ var vnfdData = data.collection[projectPrefix + "vnfd:vnfd"];
+ if (vnfdData.constructor.name == "Object") {
+ vnfdData = [vnfdData];
}
- resolve(nsdData);
+ var vnfdDict = {};
+ vnfdData.map(function(v, i) {
+ vnfdDict[v.id] = v;
+ })
+ resolve(vnfdDict);
};
- })
+ })//
})
var config = new Promise(function(resolve, reject) {
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-config/nsr' + (id ? '/' + id : '') + '?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-config/nsr' + (id ? '/' + encodeURIComponent(id) : '') + '?deep'),
method: 'GET',
headers: _.extend({}, id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
});
var opData = new Promise(function(resolve, reject) {
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata/nsr' + (id ? '/' + id : '') + '?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata/nsr' + (id ? '/' + encodeURIComponent(id) : '') + '?deep'),
method: 'GET',
headers: _.extend({}, id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
});
return new Promise(function(resolve, reject) {
//Need smarter error handling here
- Promise.all([config, opData]).then(function(resolves) {
+ Promise.all([config, opData, vnfdInfo]).then(function(resolves) {
var aggregate = {};
// resolves[0] ==> ns-instance-config
// resolves[1] ==> ns-instance-opdata
var nsInstanceConfig = resolves[0] && resolves[0];
var nsInstanceOpdata = resolves[1] && resolves[1];
+ var vnfdCatalog = resolves[2];
if (!nsInstanceConfig && !nsInstanceOpdata) {
return resolve({
'member-vnf-index-ref': vnfd['member-vnf-index-ref']
});
if (vnfrObj) {
+ var vnfdId = _.findWhere(v.nsd['constituent-vnfd'], {'member-vnf-index': vnfrObj['member-vnf-index-ref']})['vnfd-id-ref'];
vnfd['short-name'] = vnfrObj['short-name'];
+ vnfd['vnfd'] = {id: vnfrObj.id, logo: vnfdCatalog[vnfdId]['logo']}
}
})
})
}
});
});
- })
+ });
+
+ v['vnfrs'] && v['vnfrs'].map(function(vnfrObj){
+ var vnfdId = _.findWhere(v.nsd['constituent-vnfd'], {'member-vnf-index': vnfrObj['member-vnf-index-ref']})['vnfd-id-ref'];
+ vnfrObj.vnfd = vnfdCatalog[vnfdId];
+ });
});
var nsrsData = nsInstanceConfig;
nsrsData.sort(function(a, b) {
function decorateNSRWithVLR(nsr, nsrVLRObject, vlr) {
var vlrObject = _.extend(nsrVLRObject, vlr);
vlrObject['vnfr-connection-point-ref'] && vlrObject['vnfr-connection-point-ref'].map(function(vnfrCP) {
- var vnfrName = nsr['vnfrs'] && _.find(nsr['vnfrs'], {id: vnfrCP['vnfr-id']})['name'];
+ var vnfrName = nsr['vnfrs'] && nsr['vnfrs'].length && _.findWhere(nsr['vnfrs'], {id: vnfrCP['vnfr-id']})['name'];
vnfrName && (vnfrCP['vnfr-name'] = vnfrName);
});
nsr['decorated-vlrs'].splice(_.sortedIndex(nsr['decorated-vlrs'], vlrObject, 'name'), 0, vlrObject);
vnfr && vnfr['vdur'] && vnfr['vdur'].map(function(vdur) {
// This console-url is what front-end will hit to generate a real console-url
- vdur['console-url'] = 'api/vnfr/' + vnfr.id + '/vdur/' + vdur.id + '/console-url';
+ vdur['console-url'] = 'api/vnfr/' + encodeURIComponent(vnfr.id) + '/vdur/' + encodeURIComponent(vdur.id) + '/console-url';
nsr['console-urls'].push({
id: vdur.id,
name: vnfr.name,
"nsr-id": nsr['ns-instance-config-ref'],
"name": vnfr['name'],
"vdur": vnfr["vdur"],
- "cloud-account": vnfr["cloud-account"]
+ "datacenter": vnfr["datacenter"]
};
var vnfrSg = nsr['vnfr-scaling-groups'];
var vnfrName = vnfr["name"];
}
}
var vnfrNfviMetrics = buildNfviGraphs(vnfr.vdur, vnfrName);
- if (vnfr['vnf-configuration'] && vnfr['vnf-configuration']['service-primitive'] && vnfr['vnf-configuration']['service-primitive'].length > 0) {
+ if (vnfr['vnf-configuration'] && vnfr['vnf-configuration']['config-primitive'] && vnfr['vnf-configuration']['config-primitive'].length > 0) {
vnfrObj['service-primitives-present'] = true;
} else {
vnfrObj['service-primitives-present'] = false;
return new Promise(function(resolve, reject) {
var requestHeaders = {};
_.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config'),
method: 'POST',
headers: requestHeaders,
forever: constants.FOREVER_ON,
return new Promise(function(resolve, reject) {
var requestHeaders = {};
_.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id,
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + encodeURIComponent(id)),
method: 'DELETE',
headers: requestHeaders,
forever: constants.FOREVER_ON,
}
var requestHeaders = {};
_.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id + '/admin-status/',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + encodeURIComponent(id) + '/admin-status/'),
method: 'PUT',
headers: requestHeaders,
json: {
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data,
{
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}
);
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id + '/scaling-group/' + scaling_group_id + '/instance',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + encodeURIComponent(id) + '/scaling-group/' + encodeURIComponent(scaling_group_id) + '/instance'),
method: 'POST',
headers: requestHeaders,
json: jsonData,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data,
{
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}
);
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + id + '/scaling-group/' + scaling_group_id + '/instance/' + scaling_instance_id,
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + encodeURIComponent(id) + '/scaling-group/' + encodeURIComponent(scaling_group_id) + '/instance/' + encodeURIComponent(scaling_instance_id)),
method: 'DELETE',
headers: requestHeaders,
forever: constants.FOREVER_ON,
_.extend(requestHeaders,
vld_id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection,
{
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}
);
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + nsr_id + '/nsd/vld' + (vld_id ? '/' + vld_id : '') +'?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + encodeURIComponent(nsr_id) + '/nsd/vld' + (vld_id ? '/' + encodeURIComponent(vld_id) : '') +'?deep'),
method: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
return new Promise(function(resolve, reject) {
var requestHeaders = {};
_.extend(requestHeaders, constants.HTTP_HEADERS.accept.data, constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- uri: utils.confdPort(api_server) + '/api/config/ns-instance-config/nsr/' + nsr_id + '/nsd/vld' + (vld_id ? '/' + vld_id : ''),
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/config/ns-instance-config/nsr/' + encodeURIComponent(nsr_id) + '/nsd/vld' + (vld_id ? '/' + encodeURIComponent(vld_id) : '')),
method: vld_id ? 'PUT' : 'POST',
headers: requestHeaders,
forever: constants.FOREVER_ON,
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
{
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}
);
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + nsr_id + '/nsd/vld/' + vld_id,
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/ns-instance-config/nsr/' + encodeURIComponent(nsr_id) + '/nsd/vld/' + encodeURIComponent(vld_id)),
method: 'DELETE',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var api_server = req.query["api_server"];
var id = req.params.id;
var uri = utils.confdPort(api_server);
- uri += APIVersion + '/api/operational/vnfr-catalog/vnfr' + (id ? '/' + id : '') + '?deep';
+ uri += APIVersion + '/api/operational/vnfr-catalog/vnfr' + (id ? '/' + encodeURIComponent(id) : '') + '?deep';
var headers = _.extend({}, id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
return new Promise(function(resolve, reject) {
request({
- url: uri,
+ url: utils.projectContextUrl(req, uri),
method: 'GET',
headers: headers,
forever: constants.FOREVER_ON,
}, function(error, response, body) {
if (utils.validateResponse('VNFR.get', error, response, body, resolve, reject)) {
var data = JSON.parse(response.body);
- var returnData = id ? [data["vnfr:vnfr"]] : data.collection["vnfr:vnfr"];
+ var returnData = id ? (data["vnfr:vnfr"] ? [data["vnfr:vnfr"]] : []) : data.collection["vnfr:vnfr"];
returnData.forEach(function(vnfr) {
- vnfr['nfvi-metrics'] = buildNfviGraphs(vnfr.vdur);
- vnfr['epa-params'] = epa_aggregator(vnfr.vdur);
- vnfr['service-primitives-present'] = (vnfr['vnf-configuration'] && vnfr['vnf-configuration']['service-primitive'] && vnfr['vnf-configuration']['service-primitive'].length > 0) ? true : false;
+ vnfr['nfvi-metrics'] = vnfr.vdur ? buildNfviGraphs(vnfr.vdur) : [];
+ vnfr['epa-params'] = vnfr.vdur ? epa_aggregator(vnfr.vdur) : [];
+ vnfr['service-primitives-present'] = (vnfr['vnf-configuration'] && vnfr['vnf-configuration']['config-primitive'] && vnfr['vnf-configuration']['config-primitive'].length > 0) ? true : false;
vnfr['vdur'] && vnfr['vdur'].map(function(vdur, vdurIndex) {
// This console-url is what front-end will hit to generate a real console-url
- vdur['console-url'] = 'api/vnfr/' + vnfr.id + '/vdur/' + vdur.id + '/console-url';
+ vdur['console-url'] = 'api/vnfr/' + encodeURIComponent(vnfr.id) + '/vdur/' + encodeURIComponent(vdur.id) + '/console-url';
});
});
return resolve(returnData);
var uri = utils.confdPort(api_server);
var reqClone = _.clone(req);
delete reqClone.params.id;
- uri += APIVersion + '/api/operational/ns-instance-opdata/nsr/' + id + '?deep';
+ uri += APIVersion + '/api/operational/ns-instance-opdata/nsr/' + encodeURIComponent(id) + '?deep';
var headers = _.extend({}, id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
return new Promise(function(resolve, reject) {
if (VNFR.cachedNSR[id]) {
});
} else {
request({
- url: uri,
+ url: utils.projectContextUrl(req, uri),
method: 'GET',
headers: headers,
forever: constants.FOREVER_ON,
var api_server = req.query["api_server"];
var id = req.params.id;
var uri = utils.confdPort(api_server);
- uri += APIVersion + '/api/operational/vlr-catalog/vlr' + (id ? '/' + id : '') + '?deep';
+ uri += APIVersion + '/api/operational/vlr-catalog/vlr' + (id ? '/' + encodeURIComponent(id) : '') + '?deep';
var headers = _.extend({}, id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
return new Promise(function(resolve, reject) {
request({
- url: uri,
+ url: utils.projectContextUrl(req, uri),
method: 'GET',
headers: headers,
forever: constants.FOREVER_ON,
var url = req.path;
return new Promise(function(resolve, reject) {
request({
- url: uri + url + '?deep',
+ url: utils.projectContextUrl(req, uri + url + '?deep'),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
return new Promise(function(resolve, reject) {
var nsrPromise = new Promise(function(success, failure) {
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata/nsr/' + nsr_id + '?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ns-instance-opdata/nsr/' + encodeURIComponent(nsr_id) + '?deep'),
method: 'GET',
headers: _.extend({},
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
vnfrPromises.push(
new Promise(function(success, failure) {
rp({
- uri: utils.confdPort(api_server) + APIVersion + '/api/operational/vnfr-catalog/vnfr/' + vnfrId + '?deep',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/vnfr-catalog/vnfr/' + encodeURIComponent(vnfrId) + '?deep'),
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
var uri = utils.confdPort(api_server);
uri += APIVersion + '/api/operational/network?deep';
var headers = _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
return new Promise(function(resolve, reject) {
request({
- url: uri,
+ url: utils.projectContextUrl(req, uri),
method: 'GET',
headers: headers,
forever: constants.FOREVER_ON,
var vnfrID = req.params.vnfr_id;
var vdurID = req.params.vdur_id;
var uri = utils.confdPort(api_server);
- uri += APIVersion + '/api/operational/vnfr-catalog/vnfr/' + vnfrID + '/vdur/' + vdurID + '?deep';
+ uri += APIVersion + '/api/operational/vnfr-catalog/vnfr/' + encodeURIComponent(vnfrID) + '/vdur/' + encodeURIComponent(vdurID) + '?deep';
var headers = _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
return new Promise(function(resolve, reject) {
request({
- url: uri,
+ url: utils.projectContextUrl(req, uri),
method: 'GET',
headers: headers,
forever: constants.FOREVER_ON,
var vnfrID = req.params.vnfr_id;
var vdurID = req.params.vdur_id;
var uri = utils.confdPort(api_server);
- uri += APIVersion + '/api/operational/vnfr-console/vnfr/' + vnfrID + '/vdur/' + vdurID + '/console-url' + '?deep';
+ uri += APIVersion + '/api/operational/vnfr-console/vnfr/' + encodeURIComponent(vnfrID) + '/vdur/' + encodeURIComponent(vdurID) + '/console-url' + '?deep';
var headers = _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
return new Promise(function(resolve, reject) {
request({
- url: uri,
+ url: utils.projectContextUrl(req, uri),
method: 'GET',
headers: headers,
forever: constants.FOREVER_ON,
var uri = utils.confdPort(api_server);
uri += APIVersion + '/api/operational/cloud/account?deep';
var headers = _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
return new Promise(function(resolve, reject) {
request({
- url: uri,
+ url: utils.projectContextUrl(req, uri),
method: 'GET',
headers: headers,
forever: constants.FOREVER_ON,
});
});
}
+ResourceOrchestratorAccount.get = function(req) {
+var self = this;
+ var api_server = req.query["api_server"];
+ var accountID = req.params.id || req.params.name;
+
+ return new Promise(function(resolve, reject) {
+ var requestHeaders = {};
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.collection, {
+ 'Authorization': req.session && req.session.authorization
+ }
+ );
+ var urlOp = utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ro-account/account');
+ var urlConfig = utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/ro-account-state/account');
+ if(accountID) {
+ urlOp = url + '/' + encodeURIComponent(accountID);
+ urlConfig = url + '/' + encodeURIComponent(accountID);
+ }
+ var allRequests = [];
+ var roOpData = new Promise(function(resolve, reject) {
+ request({
+ url: urlOp,
+ type: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false
+ },
+ function(error, response, body) {
+ var data;
+ if (utils.validateResponse('RoAccount.get', error, response, body, resolve, reject)) {
+ try {
+ data = JSON.parse(response.body).collection['rw-ro-account:account']
+ } catch (e) {
+ console.log('Problem with "RoAccount.get"', e);
+ var err = {};
+ err.statusCode = 500;
+ err.errorMessage = {
+ error: 'Problem with "RoAccount.get": ' + e // + e.toString()
+ }
+ return reject(err);
+ }
+ return resolve({
+ statusCode: response.statusCode,
+ data: data
+ });
+ };
+ }
+ );
+ });
+ var roConfigData = new Promise(function(resolve, reject){
+ request({
+ url: urlConfig,
+ type: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false
+ },
+ function(error, response, body) {
+ var data;
+ if (utils.validateResponse('RoAccount.get', error, response, body, resolve, reject)) {
+ try {
+ data = JSON.parse(response.body).collection['rw-ro-account:account']
+ } catch (e) {
+ console.log('Problem with "RoAccount.get"', e);
+ var err = {};
+ err.statusCode = 500;
+ err.errorMessage = {
+ error: 'Problem with "RoAccount.get": ' + e // + e.toString()
+ }
+ return reject(err);
+ }
+ return resolve({
+ statusCode: response.statusCode,
+ data: data
+ });
+ };
+ }
+ );
+ });
+
+ allRequests.push(roOpData);
+ allRequests.push(roConfigData);
+ Promise.all(allRequests).then(function(data) {
+ var state = data[1].data;
+ var op = data[0].data;
+ var result = [];
+ var dict = {"rift":{}};
+ if (!accountID) {
+ state.length && state.map(function(s){
+ dict[s.name] = s;
+ });
+ op.length && op.map(function(o) {
+ dict[o.name] = _.extend(dict[o.name], o);
+ });
+ Object.keys(dict).map(function(d) {
+ result.push(dict[d]);
+ })
+ } else {
+ result = _.extend(op, state);
+ }
+ resolve({
+ statusCode: 200,
+ data: result
+ })
+ })
+
+ })
+}
// Config-Agent Account APIs
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/operational/config-agent/account',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/config-agent/account'),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/operational/config-agent/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/config-agent/account/' + encodeURIComponent(id)),
type: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/config/config-agent',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/config-agent'),
method: 'POST',
headers: requestHeaders,
forever: constants.FOREVER_ON,
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/config/config-agent/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/config-agent/account/' + encodeURIComponent(id)),
method: 'PUT',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/config/config-agent/account/' + id,
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/config-agent/account/' + encodeURIComponent(id)),
method: 'DELETE',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/operational/datacenters?deep',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/operational/datacenters?deep'),
method: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
var requestHeaders = {};
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
request({
- url: utils.confdPort(api_server) + APIVersion + '/api/config/key-pair?deep',
+ url: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/key-pair?deep'),
method: 'GET',
headers: requestHeaders,
forever: constants.FOREVER_ON,
console.log('Deleting ssk-key', id);
return new Promise(function(resolve, reject) {
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/' + id,
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/' + encodeURIComponent(id)),
method: 'DELETE',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
var data = req.body;
return new Promise(function(resolve, reject) {
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/'),
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
json: data,
forever: constants.FOREVER_ON,
var data = req.body;
return new Promise(function(resolve, reject) {
request({
- uri: utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/',
+ uri: utils.projectContextUrl(req, utils.confdPort(api_server) + APIVersion + '/api/config/key-pair/'),
method: 'PUT',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
json: data,
forever: constants.FOREVER_ON,
module.exports.networkTopology = NetworkTopology;
module.exports.config = Config;
module.exports.cloud_account = CloudAccount;
+module.exports.ResourceOrchestratorAccount = ResourceOrchestratorAccount;
module.exports['config-agent-account'] = ConfigAgentAccount;
module.exports.rpc = RPC;
module.exports.data_centers = DataCenters;
"dashboard": "./launchpad.jsx",
"order": 1,
"priority":1,
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-project:project-admin",
+ "rw-project:project-oper",
+ "rw-project-mano:lcm-oper",
+ "rw-project-mano:lcm-admin"],
"routes": [
{
"label": "Dashboard",
"component": "./instantiate/instantiateDashboard.jsx",
"path": "",
"type": "external",
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-project:project-admin",
+ "rw-project-mano:lcm-admin"
+ ],
"routes": [
{
"label": "Instantiate",
"route": ":nsd",
"component": "./instantiate/instantiateParameters.jsx",
"path": ":nsd",
- "type": "internal"
+ "type": "internal",
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-project:project-admin",
+ "rw-project-mano:lcm-admin"
+ ]
}
]
},{
"normalizr": "^2.1.0",
"open-iconic": "^1.1.1",
"prismjs": "^1.4.1",
- "react": "^0.14.8",
"react-awesome-modal": "^0.3.3",
"react-breadcrumbs": "^1.3.9",
"react-crouton": "^0.2.7",
- "react-dom": "^0.14.6",
"react-router": "^2.0.1",
"react-slick": "^0.11.1",
"react-tabs": "^0.8.0",
utils.sendErrorResponse(error, res);
})
});
+ app.get('/api/ro-account', cors(), function(req, res) {
+ launchpadAPI['ResourceOrchestratorAccount'].get(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ })
+ });
app.get('/api/config', cors(), function(req, res) {
launchpadAPI['config'].get(req).then(function(data) {
utils.sendSuccessResponse(data, res);
PLUGIN_NAME=launchpad
# 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 --optimize-minimize --progress --config webpack.production.config.js
-echo 'Packaging '$PLUGIN_NAME' using webpack... done'
-echo 'Building plugin '$PLUGIN_NAME'... done'
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
#!/bin/bash
-#
+#
# Copyright 2016 RIFT.IO Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
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
+tar -cf $bcache_dir/plugins/$plugin/node_modules.tar node_modules package.json -C $source_dir
#cp -Lrp $source_dir/node_modules $bcache_dir/plugins/$plugin/.
import CatalogDescriptorRaw from './catalogDescriptorRaw.jsx'
import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
import {Panel, PanelWrapper} from 'widgets/panel/panel';
-import Button from 'widgets/button/rw.button.js'
+import Button from 'widgets/button/rw.button.js';
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
import 'style/layout.scss';
import './instantiateDashboard.scss';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
class InstantiateDashboard extends React.Component {
constructor(props) {
super(props);
let self = this;
let asyncOperations = []
asyncOperations.push(this.Store.getCatalog());
- asyncOperations.push(this.Store.getCloudAccount(function() {
- asyncOperations.push(self.Store.getDataCenters());
- asyncOperations.push(self.Store.getResourceOrchestrator());
+ asyncOperations.push(this.Store.getResourceOrchestratorAccounts(function() {
asyncOperations.push(self.Store.getSshKey());
asyncOperations.push(self.Store.getConfigAgent());
- asyncOperations.push(self.Store.getResourceOrchestrator());
}));
Promise.all(asyncOperations).then(function(resolve, reject) {
if(self.props.params.nsd) {
self.Store.descriptorSelected(self.state.nsdDict[self.props.params.nsd]);
}
})
-
}
componentWillMount() {
this.Store.listen(this.updateState);
let html;
let selectedNSDid = self.state.selectedNSDid;
let isPreviewing = self.state.isPreviewing;
+ const hasAccess = isRBACValid(this.context.userProfile, [PROJECT_ROLES.LCM_ADMIN, PROJECT_ROLES.PROJECT_ADMIN]);
let descriptorPreview = (
<Panel title={(self.state.selectedNSD['short-name'] || self.state.selectedNSD.name ) + ' Descriptor Preview'} className="CatalogDescriptorPreview">
<span className="oi CatalogDescriptorPreview-button" data-glyph={"circle-x"} onClick={self.Store.deselectDescriptor}></span>
<Button label="Cancel" onClick={this.handleCancel}/>
{this.isSelectPage() ?
- <Button label="Next" isLoading={this.state.isSaving} onClick={this.state.selectedNSD && self.openDescriptor} className="dark" type="submit"/>
+ <Button label="Next" isLoading={this.state.isSaving} onClick={this.state.selectedNSD && self.openDescriptor} className="dark" type="submit"/>
: <div>
<Button label="Back" onClick={this.handleBack}/>
- <Button label="Launch" isLoading={this.state.isSaving} onClick={self.handleSave.bind(self, true)} className="dark" type="submit"/>
+ { hasAccess ? <Button label="Launch" isLoading={this.state.isSaving} onClick={self.handleSave.bind(self, true)} className="dark" type="submit"
+ /> : null}
</div>
}
</div>
}
}
InstantiateDashboard.contextTypes = {
- router: React.PropTypes.object
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
};
export default SkyquakeComponent(InstantiateDashboard);
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* limitations under the License.
*
*/
+
.application.instantiate {
/* overflow:visible !important;*/
height:100%;
nsConfigHTML = (props) => {
return (
<div className="configure-nsd_section">
+ <div className="noticeSubText noticeSubText_right">
+ * required
+ </div>
<div className="inputControls">
- <TextInput label="Instance Name" type="text" pattern="^[a-zA-Z0-9_]*$" style={{textAlign:'left'}} onChange={props.updateName} value={props.name}/>
- {
- !isOpenMano(props.ro) ?
- (
- <label>Select VIM Account
- <SelectOption options={constructCloudAccountOptions(props.cloudAccounts)} onChange={props.nsFn.updateSelectedCloudAccount} />
- </label>
- )
- : null
- }
+ <TextInput label="Instance Name" required={true} type="text" pattern="^[a-zA-Z0-9_]*$" style={{textAlign:'left'}} onChange={props.updateName} value={props.name}/>
+ <label>Resource Orchestrator
+ <SelectOption options={constructROOptions(props.resourceOrchestrators)} onChange={props.nsFn.updateSelectedRoAccount} />
+ </label>
{
- isOpenMano(props.ro) ?
- dataCentersHTML(props.dataCenters[props.ro.name],
- props.nsFn.updateSelectedDataCenter)
- : null
+ (props.selectedResourceOrchestrator.datacenters && props.selectedResourceOrchestrator.datacenters.datacenters) ?
+ (
+ <label>Datacenter
+ <SelectOption options={constructDataCenterOptions(props.selectedResourceOrchestrator.datacenters.datacenters)} onChange={props.nsFn.updateSelectedDataCenter} />
+ </label>
+ )
+ : <span>No datacenters configured</span>
}
</div>
</div>
}
return (
<div className="inputControls" key={i}>
- <h4 className="inputControls-title">VNFD: {v.name}</h4>
+ <h4 className="inputControls-title">VNFD: {v['vnf-name']}</h4>
{
- !isOpenMano(props.ro) ?
- (
- <label>Select VIM Account
- <SelectOption options={constructCloudAccountOptions(props.cloudAccounts)} initial={true} onChange={props.vnfFn.updateSelectedCloudAccount.bind(null, v['member-vnf-index'])} defaultValue={defaultValue} />
- </label>
- )
- : null
+ (props.selectedResourceOrchestrator.datacenters && props.selectedResourceOrchestrator.datacenters.datacenters) ?
+ (
+ <label>Datacenter
+ <SelectOption options={constructDataCenterOptions(props.selectedResourceOrchestrator.datacenters.datacenters)} onChange={props.vnfFn.updateSelectedDataCenter.bind(null, v['member-vnf-index'])} initial={true} />
+ </label>
+ )
+ : <span>No datacenters configured</span>
}
- {
- isOpenMano(props.ro) ?
- dataCentersHTML(
- props.dataCenters[props.ro.name],
- props.vnfFn.updateSelectedDataCenter.bind(null, v['member-vnf-index']), true)
- : null
- }
{
(props.configAgentAccounts && props.configAgentAccounts.length > 0) ?
- <label>Select Config Agent Account
+ <label>Config Agent Account
<SelectOption options={props.configAgentAccounts && props.configAgentAccounts.map(function(c) {
return {
label: c.name,
</div>
)
}
+ //"vnfd-catalog/vnfd[id=../../../constituent-vnfd[0]/vnfd-id-ref]/version";
+
inputParametersHTML = (props) => {
let inputParameters = props.inputParameters;
const handleChange = (i, event) => props.updateInputParam(i, event.target.value);
);
return nsinput;
}
+ //"vnfd-catalog/vnfd[id=../../../constituent-vnfd[0]/vnfd-id-ref]/version";
+
+ vnfInputParametersHTML = (props) => {
+ let vnfInputParams = props.vnfInputParams;
+ const handleChange = (vnfIndex, parameterIndex, event) => props.updateVnfInputParam(vnfIndex, parameterIndex, event.target.value);
+ let vnfInputParamsHTML = [];
+ vnfInputParams && vnfInputParams.map(function(input, i) {
+ vnfInputParamsHTML.push(
+ <div className="configure-nsd_section" key={i}>
+ <h3 className="launchpadCard_title">{`VNF Input Parameters: ${input['name']}:${input['member-vnf-index-ref']}`}</h3>
+ {
+ input['input-parameter'].filter(function(p){
+ let regex = /vnfd\[(?:vnfd:)?id\s*=\s*'?"?(\S+?)'?"?\s*\]/
+ // if has id, then it belongs in a vnfd section
+ if (p.xpath.match(regex)) {
+ // look for matching vnfd
+ if ((p.xpath.match(regex)[1] == input['vnfd-id-ref'])) {
+ return true
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }).map(function(p, j){
+ let param = vnfInputParams[i]['input-parameter'][j];
+ return (<div className="inputControls" key={j}>
+ <TextInput label={(p.label || p.xpath)} value={param.value || param['default-value']} type="text" onChange={handleChange.bind(this, i, j)} />
+ </div>)
+ })
+ }
+ </div>
+ );
+ })
+
+ return <div>{vnfInputParamsHTML}</div>;
+ }
nsPlacementGroupsHTML = (props) => {
let nsPlacementGroups = props.nsPlacementGroups;
let displayPlacementGroups = props.displayPlacementGroups;
return (
<div className="input_group" key={j}>
- <TextInput type="text" onChange={props.nsFn.placementGroupUpdate.bind(self, i, j, 'key')} placeholder="KEY" value={key} />
- <TextInput type="text" onChange={props.nsFn.placementGroupUpdate.bind(self, i, j, 'value')} placeholder="VALUE" value={value} />
+ <TextInput type="text" onChange={props.nsFn.hostAggregateUpdate.bind(self, i, j, 'key')} placeholder="KEY" value={key} />
+ <TextInput type="text" onChange={props.nsFn.hostAggregateUpdate.bind(self, i, j, 'value')} placeholder="VALUE" value={value} />
<span onClick={props.nsFn.removeHostAggregate.bind(self, i, j)} className="removeInput"><img src={imgRemove} />Remove</span>
</div>
)
</div>
{
isUnknown ? null : isVIM ?
- <TextInput label="Network Name" onChange={self.props.vldFn.updateValue(i, currentType)} value={v[currentType]} /> :
- <div>
+ <div>
+ <TextInput label="Network Name" onChange={self.props.vldFn.updateValue(i, currentType)} value={v[currentType]} />
+ {
+ v['mgmt-network'].toUpperCase() == "TRUE" ?
+ <TextInput label="IPV4 NAT POOL NAME" placeholder={self.props.dataCenterID} onChange={self.props.vldFn.updateValue(i, 'ipv4-nat-pool-name')} value={v['ipv4-nat-pool-name']} />
+ : null
+ }
+ </div>
+ : <div>
<SelectOption
label="IP PROFILE NAME"
options={ipProfileList && ipProfileList.map(function(ip) {
<div className="input_group input_group-users" key={i}>
<div className="inputControls">
<div style={{fontWeight: 'bold', display: 'flex'}}>USER <span onClick={usersFn.remove(i)} className="removeInput"><img src={imgRemove} />Remove</span></div>
+
<TextInput onChange={usersFn.update(i, 'name')} label="USERNAME" value={i.name} />
<TextInput onChange={usersFn.update(i, 'user-info')} label="REAL NAME" value={i.gecos} />
{
this.inputParametersHTML(props)
}
{
+ //VNF INPUT PARAMETERS
+ this.vnfInputParametersHTML(props)
+ }
+ {
+ true ?
+ // self.props.selectedResourceOrchestrator['ro-account-type'] == 'rift-ro' ?
//NS PLACEMENTGROUPS
- this.nsPlacementGroupsHTML(props)
+ this.nsPlacementGroupsHTML(props) : null
}
{
+ true ?
+ // self.props.selectedResourceOrchestrator['ro-account-type'] == 'rift-ro' ?
//VNF PLACEMENTGROUPS
- this.vnfPlacementGroupsHTML(props)
+ this.vnfPlacementGroupsHTML(props) : null
}
{
//VLD CONFIGURATION
}
function addDNS(){}
function removeDNS(){}
+function constructROOptions(resourceOrchestrators){
+ let ROOptions = resourceOrchestrators && resourceOrchestrators.map(function(ro, index) {
+ return {
+ label: ro.name,
+ value: ro
+ }
+ });
+ return ROOptions;
+}
function constructCloudAccountOptions(cloudAccounts){
let CloudAccountOptions = cloudAccounts && cloudAccounts.map(function(ca, index) {
return {
});
return CloudAccountOptions;
}
+function constructDataCenterOptions(dataCenters){
+ let DataCenterOptions = dataCenters && dataCenters.map(function(dc, index) {
+ return {
+ label: dc.name,
+ value: dc.name
+ }
+ });
+ return DataCenterOptions;
+}
function dataCentersHTML(dataCenters, onChange, initial) {
//Build DataCenter options
//Relook at this, why is it an object?
});
if (dataCenters && dataCenters.length > 0) {
return (
- <label>Select Data Center
+ <label>Data Center
<SelectOption initial={!!initial} options={DataCenterOptions} onChange={onChange} />
</label>
)
}
}
-function isOpenMano(account) {
- if (account) {
- let a = account;
- if (a.constructor.name == 'String') {
- a = JSON.parse(a);
- }
- return a['account-type'] == 'openmano';
- } else {
- return false;
- }
-}
function updateNewSshKeyRefSelection(e) {
this.setState({
newRefSelection: e.target.value
sshFn={this.props.sshFn()}
updateName={this.props.nameUpdated}
updateInputParam={this.props.updateInputParam}
-
+ updateVnfInputParam={this.props.updateVnfInputParam}
nsd={selectedNSD}
selectedNSDid={this.props.selectedNSDid}
name={this.props.name}
dataCenters={this.props.dataCenters}
configAgentAccounts={this.props.configAgentAccounts}
inputParameters={this.props['input-parameters']}
+ vnfInputParams={this.props['vnf-input-parameter']}
displayPlacementGroups={this.props.displayPlacementGroups}
isOpenMano={this.props.isOpenMano}
+ displayVIMAccounts={this.props.displayVIMAccounts}
+ resourceOrchestrators={this.props.resourceOrchestrators}
+
+ selectedResourceOrchestrator={this.props.selectedResourceOrchestrator}
+ selectedDataCenterID={this.props.dataCenterID}
+
+ vnfDataCenters={this.props.vnfDataCenters}
/>
</Panel>
</PanelWrapper>
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
return (
<Panel title="Select Descriptor" className={"InstantiateSelectDescriptorPanel" + (isPreviewing ? " InstantiateSelectDescriptorPanel--previewmode" : '')}>
{
- catalog.descriptors && catalog.descriptors.map(function(descriptor, i) {
- let isSelected = (descriptor.id === selectedDescriptorID);
- return (
- <CatalogCard
- key={i}
- isActive={isPreviewing && isSelected}
- isSelected={isSelected}
- descriptor={descriptor}
- onClick={onSelectDescriptor.bind(null, descriptor)}
- onDoubleClick={openDescriptor.bind(null, descriptor)}
- onPreviewDescriptor={onPreviewDescriptor}
- onCloseCard={closeCard}
- />
- )
- })
+ catalog.descriptors && (catalog.descriptors.length > 0) ?
+ catalog.descriptors.map(function(descriptor, i) {
+ let isSelected = (descriptor.id === selectedDescriptorID);
+ return (
+ <CatalogCard
+ key={i}
+ isActive={isPreviewing && isSelected}
+ isSelected={isSelected}
+ descriptor={descriptor}
+ onClick={onSelectDescriptor.bind(null, descriptor)}
+ onDoubleClick={openDescriptor.bind(null, descriptor)}
+ onPreviewDescriptor={onPreviewDescriptor}
+ onCloseCard={closeCard}
+ />
+ )
+ })
+ : <div className="InstantiateSelectDescriptorPanel-message"><h2>No Descriptors Onboarded</h2></div>
}
</Panel>
)
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* limitations under the License.
*
*/
+@import "style/_colors.scss";
+
.InstantiateSelectDescriptorPanel {
display: -ms-flexbox;
display: flex;
box-sizing: border-box;
overflow:auto;
}
+ &-message {
+ display:-ms-flexbox;
+ display:flex;
+ -ms-flex-pack: center;
+ justify-content: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -ms-flex: 1;
+ flex: 1;
+
+ h2 {
+ text-transform: uppercase;
+ color: $neutral-dark-2;
+ }
+ }
}
import Alt from '../alt';
import _cloneDeep from 'lodash/cloneDeep';
import _find from 'lodash/find';
-
+import _merge from 'lodash/merge';
class LaunchNetworkServiceStore {
constructor() {
this.sla_parameters = [];
this.selectedNSDid;
this.selectedNSD = {};
- this.selectedCloudAccount = {};
this.dataCenters = [];
- this.cloudAccounts = [];
this.isLoading = false;
this.hasConfigureNSD = false;
+ this.hasConfigureVNFD = false;
this['input-parameters'] = [];
- this.displayPlacementGroups = false;
- this.ro = {};
+ this.displayPlacementGroups = true;
this.bindActions(NetworkServiceActions);
this.nsdConfiguration = {
name:'',
- selectedCloudAccount: {},
dataCenterID: null
};
/*Collection of vnf state containting cloud account and datacenter info.
keyed off vnfd-id-ref
*/
- this.vnfdCloudAccounts = {};
this.usersList = [];
this.configAgentAccounts = [];
this.isPreviewing = false;
this.isOpenMano = false;
+
+ this.displayVIMAccounts = false;
+ this.resourceOrchestrators = [{
+ name: 'rift',
+ 'ro-account-type': 'rift-ro'
+ }];
+ this.selectedResourceOrchestrator = this.resourceOrchestrators[0];
+ this.dataCenterID = null;
+ this.vnfDataCenters = {};
this.registerAsync(NetworkServiceSource);
this.exportPublicMethods({
getMockData: getMockData.bind(this),
name: '',
'input-parameter-xpath': null,
'ns-placement-groups': null,
- 'vnf-placement-groups':null,
- vnfdCloudAccounts: {}
+ 'vnf-placement-groups':null
})
}
});
}
- getLaunchCloudAccountSuccess(cloudAccounts) {
- let newState = {};
- newState.cloudAccounts = cloudAccounts.filter(function(v) {
- console.log(v)
- return v['connection-status'].status == 'success';
- }) || [];
- if(cloudAccounts.length != newState.cloudAccounts.length) {
- Alt.actions.global.showNotification.defer({type: 'warning', msg: 'One or more VIM accounts have failed to connect'});
- }
- if(cloudAccounts && cloudAccounts.length > 0) {
- newState.selectedCloudAccount = newState.cloudAccounts[0];
- if (cloudAccounts[0]['account-type'] == 'openstack') {
- newState.displayPlacementGroups = true;
- } else {
- newState.displayPlacementGroups = false;
- }
- } else {
- newState.selectedCloudAccount = {};
- }
-
- this.setState(newState);
- }
getConfigAgentSuccess(configAgentAccounts) {
this.setState({
configAgentAccounts: configAgentAccounts
let newState = {
dataCenters: dataCenters || []
};
- if (this.ro && this.ro['account-type'] == 'openmano') {
+ if (this.ro && this.ro['account-type'] == 'openmano') {
newState.dataCenterID = dataCenters[this.ro.name][0].uuid
}
this.setState(newState)
return window.location.hash = 'launchpad/' + tokenizedHash[2];
}
launchNSRError(data) {
- var msg = 'Something went wrong while trying to instantiate. Check the error logs for more information';
- if(data) {
- msg = data;
- }
- if (data.error) {
- msg = data.error;
- }
- Alt.actions.global.showNotification.defer(msg);
+ Alt.actions.global.showNotification.defer(data);
Alt.actions.global.hideScreenLoader.defer();
this.setState({
isLoading: false
sshKeysRef: []
})
}
- getResourceOrchestratorSuccess = (data) => {
+ getResourceOrchestratorAccountsSuccess = (data) => {
+ let self = this;
Alt.actions.global.hideScreenLoader.defer();
+ let ROAccounts = [];
this.setState({
- ro: data
+ resourceOrchestrators: ROAccounts.concat(data),
+ selectedResourceOrchestrator: data[0],
+ dataCenterID: data && data[0] && data[0].datacenters && data[0].datacenters.datacenters && data[0].datacenters.datacenters[0] && data[0].datacenters.datacenters[0].name,
+ dataCenterType: data && data[0] && data[0].datacenters && data[0].datacenters.datacenters && data[0].datacenters.datacenters[0] && data[0].datacenters.datacenters[0]['datacenter-type'],
+ displayVIMAccounts: false
})
}
getResourceOrchestratorError = (data) => {
};
newState.selectedNSD = data;
newState['input-parameters'] = [];
+ newState['vnf-input-parameter'] = [];
if (NSD['input-parameter-xpath']) {
+ let vnfParameters = [];
newState.hasConfigureNSD = true;
NSD['input-parameter-xpath'].map(function(p) {
- newState.hasConfigureNSD = true;
- newState['input-parameters'].push(_cloneDeep(p));
+ if (isVNFDInputParameter(p)) {
+ newState.hasConfigureVNFD = true;
+ vnfParameters.push(p);
+ } else {
+ newState.hasConfigureNSD = true;
+ newState['input-parameters'].push(p);
+ }
+ })
+ newState['vnf-input-parameter'] = NSD['constituent-vnfd'].map(function(vnf) {
+ return {
+ 'name': vnf['vnf-name'],
+ 'member-vnf-index-ref': vnf['member-vnf-index'],
+ 'vnfd-id-ref': vnf['vnfd-id-ref'],
+ 'input-parameter':_cloneDeep(vnfParameters)
+ }
})
} else {
newState.hasConfigureNSD = false;
- newState['input-parameters'] = null;
+ newState['input-parameter'] = null;
}
if(NSD['ns-placement-groups'] && NSD['ns-placement-groups'].length > 0 ) {
newState['ns-placement-groups'] = NSD['ns-placement-groups'];
if(NSD['vnf-placement-groups'] && NSD['vnf-placement-groups'].length > 0 ) {
newState['vnf-placement-groups'] = NSD['vnf-placement-groups'];
}
- NSD["constituent-vnfd"].map((v) => {
+ NSD["constituent-vnfd"] && NSD["constituent-vnfd"].map((v) => {
VNFIDs.push(v["vnfd-id-ref"]);
})
this.getInstance().getVDU(VNFIDs);
this.setState(newState);
+
+ function isVNFDInputParameter(p){
+ return p.xpath.match('vnfd-catalog');
+ }
}
previewDescriptor = (data) => {
let self = this;
'input-parameters': ip
});
}
+ updateVnfInputParam = (i, j, value) => {
+ let ip = this['vnf-input-parameter'];
+ ip[i]['input-parameter'][j].value = value;
+ this.setState({
+ 'vnf-input-parameter': ip
+ });
+ }
nsFn = () => {
let self = this;
return {
- updateSelectedCloudAccount: (cloudAccount) => {
+ updateSelectedRoAccount: (resourceOrchestrator) => {
let nsd = self.nsd[0];
var newState = {
- selectedCloudAccount: JSON.parse(cloudAccount.target.value)
+ selectedResourceOrchestrator: JSON.parse(JSON.parse(resourceOrchestrator.target.value))
};
- if (cloudAccount['account-type'] == 'openstack') {
- newState.displayPlacementGroups = true;
+ // if no datacenters and ro is not rift-ro
+ if (!(newState.selectedResourceOrchestrator.datacenters && newState.selectedResourceOrchestrator.datacenters.datacenters) ) {
+ Alt.actions.global.showNotification.defer("No data centers configured in resource orchestrator");
} else {
- newState.displayPlacementGroups = false;
+ newState.dataCenterID = newState.selectedResourceOrchestrator.datacenters.datacenters[0].name;
+ newState.dataCenterType = newState.selectedResourceOrchestrator.datacenters.datacenters[0]['datacenter-type'];
+ newState.displayPlacementGroups = newState.selectedResourceOrchestrator.name == "rift" ? true : false;
}
self.setState(newState);
},
updateSelectedDataCenter: (dataCenter) => {
+ let dataCenterID = JSON.parse(JSON.parse(dataCenter.target.value));
+ let dataCenterType = _find(self.selectedResourceOrchestrator.datacenters.datacenters, {name: dataCenterID})
+
self.setState({
- dataCenterID: JSON.parse(dataCenter.target.value)
+ dataCenterID,
+ dataCenterType
});
},
- placementGroupUpdate: (i, k, value) => {
+ placementGroupUpdate: (i, k, event) => {
+ let value = event.target.value;
let pg = self['ns-placement-groups'];
pg[i][k] = value;
self.setState({
'ns-placement-groups': pg
})
},
- hostAggregateUpdate: (pgi, hai, k, value) => {
+ hostAggregateUpdate: (pgi, hai, k, event) => {
+ let value = event.target.value;
let pg = self['ns-placement-groups'];
let ha = pg[pgi]['host-aggregate'][hai];
ha[k] = value;
vnfFn = () => {
let self = this;
return {
- placementGroupUpdate: (i, k, value) => {
+ placementGroupUpdate: (i, k, event) => {
+ let value = event.target.value;
let pg = self['vnf-placement-groups'];
pg[i][k] = value;
self.setState({
'vnf-placement-groups': pg
})
},
- hostAggregateUpdate: (pgi, hai, k, value) => {
+ hostAggregateUpdate: (pgi, hai, k, event) => {
+ let value = event.target.value;
let pg = self['vnf-placement-groups'];
let ha = pg[pgi]['host-aggregate'][hai];
ha[k] = value;
'vnf-placement-groups': pg
})
},
- updateSelectedCloudAccount: (id, cloudAccount) => {
- let vnfCA = self.vnfdCloudAccounts;
- if(cloudAccount) {
- if(!vnfCA.hasOwnProperty(id)) {
- vnfCA[id] = {};
- }
- vnfCA[id].account = JSON.parse(cloudAccount.target.value);
-
- if (cloudAccount['account-type'] == 'openmano' && this.dataCenters && self.dataCenters[cloudAccount['name']]) {
- let datacenter = self.dataCenters[cloudAccount['name']][0];
- vnfCA[id].datacenter = datacenter.uuid;
- } else {
- if (vnfCA[id].datacenter) {
- delete vnfCA[id].datacenter;
- }
- }
- } else {
- if(vnfCA.hasOwnProperty(id)) {
- if(vnfCA[id].hasOwnProperty('config-agent-account')) {
- delete vnfCA[id].account;
- } else {
- delete vnfCA[id];
- }
- }
- }
- self.setState({
- vnfdCloudAccounts: vnfCA
- });
- },
updateSelectedConfigAgent: (id) => {
return function(e) {
let configAgentRef = JSON.parse(e.target.value);
- let vnfCA = self.vnfdCloudAccounts;
+ let vnfDC = self.vnfDataCenters;
if(configAgentRef) {
- if(!vnfCA.hasOwnProperty(id)) {
- vnfCA[id] = {};
+ if (!vnfDC[id]) {
+ vnfDC[id] = {};
}
- vnfCA[id]['config-agent-account'] = configAgentRef;
+ vnfDC[id]['config-agent-account'] = configAgentRef;
} else {
- if(vnfCA[id].hasOwnProperty('account')) {
+ if(vnfDC[id].hasOwnProperty('datacenter')) {
delete vnfCA[id]['config-agent-account'];
} else {
delete vnfCA[id];
}
}
self.setState({
- vnfdCloudAccounts: vnfCA
+ vnfDataCenters: vnfDC
});
}
},
updateSelectedDataCenter: (id, dataCenter) => {
- let vnfCA = self.vnfdCloudAccounts;
- if (!vnfCA[id]) {
- vnfCA[id] = {};
+ let vnfDC = self.vnfDataCenters;
+ let dc = JSON.parse(JSON.parse(dataCenter.target.value));
+ if (!vnfDC[id]) {
+ vnfDC[id] = {};
+ }
+ vnfDC[id]['member-vnf-index-ref'];
+ if (dc) {
+ vnfDC[id].datacenter = dc;
+ } else {
+ delete vnfDC[id];
}
- vnfCA[id].datacenter = JSON.parse(dataCenter.target.value);
+
self.setState({
- vnfdCloudAccounts: vnfCA
+ vnfDataCenters: vnfDC
});
}
}
let IPProfile = self.ipProfiles;
vld[i][type] = IPProfile[0] && IPProfile[0].name;
delete vld[i]['vim-network-name'];
+ delete vld[i]['ipv4-nat-pool-name'];
} else {
delete vld[i]['dns-server'];
+ vld[i]['ipv4-nat-pool-name'] = self.dataCenterID
}
if(type == 'none') {
delete vld[i]['ip-profile-ref'];
delete vld[i]['vim-network-name'];
+ delete vld[i]['ipv4-nat-pool-name'];
}
self.setState({vld:vld});
}
}
}
saveNetworkServiceRecord(name, launch) {
+ let self = this;
//input-parameter: [{uuid: < some_unique_name>, xpath: <same as you got from nsd>, value: <user_entered_value>}]
/*
'input-parameter-xpath':[{
nsdPayload.vld && nsdPayload.vld.map(function(v){
delete v['none'];
delete v.type;
- })
+ });
+ nsdPayload['input-parameter-xpath'] && nsdPayload['input-parameter-xpath'].map((x) => delete x.value);
}
- let vnfdCloudAccounts = this.state.vnfdCloudAccounts;
let payload = {
id: guuid,
"name": name,
"nsd": nsdPayload
}
- if (this.state.ro && this.state.ro['account-type'] == 'openmano') {
- payload['om-datacenter'] = this.state.dataCenterID;
- } else {
- if(!this.state.selectedCloudAccount) {
- Alt.actions.global.showNotification.defer("No VIM Account Selected");
- return;
- }
- payload["cloud-account"] = this.state.selectedCloudAccount.name;
+ if(!this.state.selectedResourceOrchestrator) {
+ Alt.actions.global.showNotification.defer("No Resource Orchestrator selected");
+ return;
+ }
+ if (this.state.selectedResourceOrchestrator.name != "rift") {
+ payload["resource-orchestrator"] = this.state.selectedResourceOrchestrator.name;
+ }
+
+ if(!this.state.dataCenterID) {
+ Alt.actions.global.showNotification.defer("No Data Center selected");
+ return;
}
+ payload["datacenter"] = this.state.dataCenterID;
+
//Clean Input Parameters
if (this.state.hasConfigureNSD) {
let ips = _cloneDeep(this.state['input-parameters']);
payload['input-parameter'] = ipsToSend;
}
}
+ //Clean VNF Input Parameters
+ if (this.state.hasConfigureVNFD) {
+ let vnf = _cloneDeep(this.state['vnf-input-parameter']);
+ vnf = vnf.filter(function(v){
+ delete v.name;
+ v['input-parameter'] = v['input-parameter'].filter(
+ function(i) {
+ if(i.value && i.value != "") {
+ delete i.label;
+ delete i['default-value'];
+ return true;
+ }
+ return false;
+ });
+ if (v['input-parameter'].length) {
+ return true;
+ }
+ return false;
+ })
+ if (vnf.length > 0) {
+ payload['vnf-input-parameter'] = vnf;
+ }
+ }
+ let VnfDataCenters = this.state.vnfDataCenters;
+ if (Object.keys(VnfDataCenters).length) {
+ payload['vnf-datacenter-map'] = Object.keys(VnfDataCenters).map(function(k) {
+ return {
+ 'member-vnf-index-ref' : k,
+ datacenter: VnfDataCenters[k].datacenter
+ }
+ })
+ }
// These placement groups need to be refactored. Too much boilerplate.
if (this.state.displayPlacementGroups) {
nsPg = this.state['ns-placement-groups'];
payload['nsd-placement-group-maps'] = nsPg.map(function(n, i) {
if(n['availability-zone'] || n['server-group'] || (n['host-aggregate'].length > 0)) {
var obj = {
- 'cloud-type': 'openstack'
+ 'cloud-type': self.state.dataCenterType
};
if(n['host-aggregate'].length > 0) {
obj['host-aggregate'] = n['host-aggregate'].map(function(h, j) {
});
};
if(vnfPg && (vnfPg.length > 0)) {
+ let vnfDataCenterDictionary = {};
+ payload['vnf-datacenter-map'] && payload['vnf-datacenter-map'].map(function(d) {
+ vnfDataCenterDictionary[d['member-vnf-index-ref']] = d.datacenter
+ })
payload['vnfd-placement-group-maps'] = vnfPg.map(function(n, i) {
if(n['availability-zone'] || n['server-group'] || (n['host-aggregate'].length > 0)) {
+ let DC = vnfDataCenterDictionary[n['member-vnf-index']];
var obj = {
- 'cloud-type': 'openstack'
+ 'cloud-type': DC ? _find(self.state.selectedResourceOrchestrator.datacenters.datacenters, {name: DC})['datacenter-type'] : self.state.dataCenterType
};
if(n['host-aggregate'].length > 0) {
obj['host-aggregate'] = n['host-aggregate'].map(function(h, j) {
});
}
}
- //Construct VNF cloud accounts
- payload['vnf-cloud-account-map'] = [];
- for(let k in vnfdCloudAccounts) {
- let vnf = {};
- vnf['member-vnf-index-ref'] = k;
- if(vnfdCloudAccounts[k].hasOwnProperty('account') && (vnfdCloudAccounts[k]['account'] && vnfdCloudAccounts[k]['account'].name)) {
- vnf['cloud-account'] = vnfdCloudAccounts[k].account.name;
- }
- if(vnfdCloudAccounts[k].hasOwnProperty('config-agent-account') && vnfdCloudAccounts[k]['config-agent-account']) {
- vnf['config-agent-account'] = vnfdCloudAccounts[k]['config-agent-account'];
- }
- if(vnfdCloudAccounts[k].hasOwnProperty('datacenter')) {
- vnf['om-datacenter'] = vnfdCloudAccounts[k].datacenter;
- }
- if(vnf['om-datacenter'] || vnf['cloud-account'] || vnf['config-agent-account']) {
- payload['vnf-cloud-account-map'].push(vnf);
- }
- }
+
//Add SSH-Keys
payload['ssh-authorized-key'] = this.state.sshKeysRef.map(function(k) {
return {'key-pair-ref': JSON.parse(k).name};
});
//Add Users
payload['user'] = addKeyPairRefToUsers(this.state.usersList);
- // console.log(payload)
+ console.log(payload)
this.launchNSR({
'nsr': [payload]
});
'getCatalogError',
'getVDUSuccess',
'getVDUError',
- 'getLaunchCloudAccountSuccess',
- 'getLaunchCloudAccountError',
'getDataCentersSuccess',
'getDataCentersError',
'launchNSRLoading',
'getInstantiateSshKeyError',
'getConfigAgentSuccess',
'getConfigAgentError',
- 'getResourceOrchestratorSuccess',
+ 'getResourceOrchestratorAccountsSuccess',
'getResourceOrchestratorAgentError'
)
success: Alt.actions.global.getCatalogSuccess,
error: Alt.actions.global.getCatalogError
},
- getCloudAccount:{
- remote (state, cb) {
- return new Promise((resolve, reject) => {
- $.ajax({
- url: 'api/cloud-account?api_server=' +
- API_SERVER,
- type: 'GET',
- beforeSend: Utils.addAuthorizationStub,
- success: function (data) {
- resolve(data);
- if(cb) {
- cb();
- }
- }
- })
- })
- },
- success: Alt.actions.global.getLaunchCloudAccountSuccess,
- error: Alt.actions.global.getLaunchCloudAccountError
- },
getDataCenters:{
remote () {
return new Promise((resolve, reject) => {
}).fail(function(xhr){
//Authentication and the handling of fail states should be wrapped up into a connection class.
Utils.checkAuthentication(xhr.status);
- var error = null;
- if(xhr.responseText) {
- try {
- error = JSON.parse(xhr.responseText);
- error = JSON.parse(error.error)['rpc-reply']['rpc-error']['error-message'];
- } catch(e){
- console.log(e);
- }
- }
- reject(error);
+ reject(xhr.responseText || 'An error occurred. Check your logs for more information');
});
})
},
remote(state) {
return new Promise((resolve, reject) => {
$.ajax({
- url: 'api/nsd/' + NSDId + '/input-param?api_server=' + API_SERVER,
+ url: 'api/nsd/' + encodeURIComponent(NSDId) + '/input-param?api_server=' + API_SERVER,
type: 'GET',
beforeSend: Utils.addAuthorizationStub,
success: function (data) {
success: Alt.actions.global.getConfigAgentSuccess,
error: Alt.actions.global.getConfigAgentError
},
- getResourceOrchestrator: {
- remote: function() {
+ getResourceOrchestratorAccounts: {
+ remote: function(state, cb) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'passthrough/data/api/running/resource-orchestrator' + '?api_server=' + API_SERVER,
+ url: 'api/ro-account' + '?api_server=' + API_SERVER,
type: 'GET',
beforeSend: Utils.addAuthorizationStub,
contentType: "application/json",
success: function(data) {
- let returnedData;
- if (data.hasOwnProperty("rw-launchpad:resource-orchestrator")) {
- returnedData = data['rw-launchpad:resource-orchestrator'];
- } else {
- returnedData = {};
+ if(cb) {
+ cb();
}
- resolve(returnedData);
+ resolve(data);
},
error: function(error) {
console.log("There was an error updating the account: ", arguments);
interceptResponse: interceptResponse({
'error': 'There was an error retrieving the resource orchestrator information.'
}),
- success: Alt.actions.global.getResourceOrchestratorSuccess,
+ success: Alt.actions.global.getResourceOrchestratorAccountsSuccess,
loading: Alt.actions.global.showScreenLoader,
- error: Alt.actions.global.showNotification
+ error: Alt.actions.global.handleServerReportedError
}
-
}
}
function interceptResponse (responses) {
+++ /dev/null
-
-/*
- *
- * Copyright 2016 RIFT.IO Inc
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-require('../components/form-controls.js');
-require('./launchpad-launch-fleet.html');
-var createStore = require('./createStore.js');
-var createActions = require('./createActions.js');
-
-
-angular.module('launchpad')
- .controller('launchpadCreateCtrl', function($timeout, $stateParams, $state) {
- var self = this;
- self.createStore =
- // var createChannel = $rw.radio.channel('createFleet');
- var apiServer = self.isOnline = require('utils/rw.js').getSearchParams(window.location).api_server;
- // var federationChannel = $rw.radio.channel('federationChannel');
- self.fleet = {
- template_id: null,
- pool_id: null,
- description: '',
- epa_attributes: {},
- status: "active",
- name: 'NEW FLEET'
- };
- self.slaParams = [];
- self.federation = $stateParams.id;
- createStore.getNetworkServices();
- createStore.getSlaParams();
- createStore.getPools();
- createStore.listen(function(state) {
- $timeout(function() {
- self.networkServices = state.networkServices;
- self.slaParams = state.slaParams;
- self.fleet.pool = state.pools[0];
- self.pools = state.pools;
- angular.forEach(self.slaParams, function(v) {
- if (!v.hasOwnProperty('value')) {
- v.value = v.options.second;
- };
- return v;
- });
- })
- })
- // federationChannel.request("federation:services").then(function(data) {
- // $timeout(function() {
- // // self.fleet.service = 'cag';
- // self.networkServices = data;
- // createChannel.request('vnfParams', 'cag').then(function(data) {
- // $timeout(function() {
- // self.vnfParams = data;
- // });
- // });
- // });
- // });
- // federationChannel.request('federation:pools', apiServer).then(function(data) {
- // $timeout(function() {
- // console.log('pools:', data)
- // self.fleet.pool = data[0];
- // self.pools = data;
- // })
- // });
-
- // federationChannel.request('federation:sla-params').then(function(data) {
- // $timeout(function() {
- // self.slaParams = data;
- // angular.forEach(self.slaParams, function(v) {
- // if (!v.hasOwnProperty('value')) {
- // v.value = v.options.second;
- // };
- // return v;
- // });
- // }
- // );
- // });
-
- // federationChannel.on("launchpadCreate", function() {
- // $state.go('launchpad', null, {reload: false});
-
- // });
-
- self.generateServiceImage = function(service) {
- return ('assets/img/svg/' + service.src + (self.isSelectedService(service.id) ? '-active' : '-inactive') + '.svg');
- };
- self.generatePoolImage = function(pool) {
- return ('assets/img/svg/' + self.refsDB.resources.openstackCloud.pools[pool.ref].src + (self.isSelectedPool(pool) ? '-active' : '-inactive') + '.svg');
- };
- self.isSelectedPool = function(id) {
- return id == self.fleet.pool_id;
- };
- self.isSelectedService = function(id) {
- return id == self.fleet.template_id;
- };
- self.launch = function(launch) {
- if (self.fleet.name == "") {
- createActions.validateError('Plase Name the Service')
- }
- createActions.validateReset();
- self.slaParams.forEach(function(v) {
- if (v.value.indexOf("RRC") > -1) {
- v.value = "RRC";
- }
- self.fleet.epa_attributes[v.ref] = v.value;
- });
- delete self.fleet.pool;
- self.fleet.status = launch ? 'active' : 'inactive';
- createStore.createEnvironment(self.fleet)
- };
- self.selectPool = function(id) {
- self.fleet.pool_id = id;
- // createChannel.command("pool:select", id);
- };
- self.selectService = function(id) {
-
- self.fleet.template_id = id;
- // createChannel.command("service:select", id);
- };
- });
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
import Crouton from 'react-crouton'
import AppHeader from 'widgets/header/header.jsx';
import './launchpad.scss';
+
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+
+const PROJECT_ROLES = ROLES.PROJECT;
+
let ReactCSSTransitionGroup = require('react-addons-css-transition-group');
var LaunchpadFleetActions = require('./launchpadFleetActions.js');
var LaunchpadFleetStore = require('./launchpadFleetStore.js');
render () {
var self = this;
+ const hasAccess = isRBACValid(this.context.userProfile, [PROJECT_ROLES.LCM_ADMIN, PROJECT_ROLES.PROJECT_ADMIN]);
let mgmtDomainName = window.location.hash.split('/')[2];
let navItems = [];
if(!mgmtDomainName) {
isVisible={self.state.isNsListPanelVisible}
/>
<NsCardPanel nsrs={self.state.nsrs}
- openedNsrIDs={self.state.openedNsrIDs} />
+ openedNsrIDs={self.state.openedNsrIDs}
+ hasAccess={hasAccess} />
</div>
</div>
);
}
}
LaunchpadApp.contextTypes = {
- router: React.PropTypes.object
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
};
LaunchpadApp.defaultProps = {
// name: 'Loading...',
console.log(id)
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + id + '?api_server=' + API_SERVER,
+ url: 'api/nsr/' + encodeURIComponent(id) + '?api_server=' + API_SERVER,
type: 'DELETE',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
return resolve(false);
}
$.ajax({
- url: '/socket-polling?api_server=' + API_SERVER,
+ url: '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
remote: function(state, id, status) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + id + '/admin-status?api_server=' + API_SERVER ,
+ url: 'api/nsr/' + encodeURIComponent(id) + '/admin-status?api_server=' + API_SERVER ,
type:'PUT',
beforeSend: Utils.addAuthorizationStub,
data: {
+++ /dev/null
-
-/*
- *
- * Copyright 2016 RIFT.IO Inc
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-require('./launchpad-fleet-card-params.html');
-var React = require('react');
-var LaunchpadCard = require('./launchpadCard.jsx');
-// var LaunchpadCard = require('../../components/dashboard_card/dashboard_card.jsx');
-angular.module('launchpad')
- .directive('fleetCard', function($state, $stateParams) {
- return {
- restrict: 'AE',
- replace: true,
- template: '<div></div>',
- scope: {
- fleet: '=data',
- $index: '=',
- metric: '=?',
- slaParam: '=?',
- nfviMetric: '=?'
- },
-
- bindToController: true,
- controllerAs: 'card',
- link: function(scope, element, attributes) {
-
- },
- controller: function($timeout, $interval, $scope, $rootScope, $element) {
- var self = this;
-
- console.log(self.fleet)
- $scope.$watch(function() {
- return self.fleet}, reactRender)
- function reactRender() {
- console.log('rendering', self.fleet)
- React.render(
- React.createElement(LaunchpadCard, {className:'launchpadCard'}
- ),
- $element[0]
- );
- }
- //FOR WAG ONLY
- // self.isOn = false;
-
- // //END WAG ONLY
- // //Remove for testing only
- // $scope.$watch(function() {
- // return self.fleet;
- // }, function() {});
-
- // // var fleetChannel = $rw.radio.channel('fleetChannel');
- // var FleetStore = require('../launchpadFleetStore.js');
- // self.valueFormat = {
- // "int": 1,
- // "dec": 0
- // };
- // self.federationID = $stateParams.id;
- // //if this is true, set gauges to start
- // if (require('utils/rw.js').getSearchParams(window.location).api_server) {
- // self.fleet.started = true;
- // }
- // self.apiServer = require('utils/rw.js').getSearchParams(window.location).api_server;
- // self.isNoisy = false;
- // var rateChanged = function(rate) {
- // self.rate = rate;
- // fleetChannel.command('fleet:change:rate', rate)
- // }
- // var packetSizeChanged = function(packetSize) {
- // self.packetSize = packetSize
- // fleetChannel.command('fleet:change:packetSize', packetSize)
- // };
- // self.openFleet = function(index) {
- // var params = require('utils/rw.js').getSearchParams(window.location);
- // if (params.api_server) {
- // if (params.api_server == "http://10.0.201.25:5000") {
- // var newLoc = window.location.origin + window.location.pathname + '?config=' + params.config + '&api_server=' + 'http://10.0.201.25:5050' + '#/wag';
- // window.open(newLoc);
- // return;
- // }
- // if (self.fleet.api == "10.0.117.22") self.fleet.api = "10.0.117.17";
- // var newLoc = window.location.origin + window.location.pathname + '?config=' + params.config + '&api_server=' + 'http://' + self.fleet.api + ':5050' + '&name=' + self.fleet.name + '&env_id=' + self.fleet.id + '#/dashboard/' + $stateParams.id + '/' + index;;
- // console.log('Opening new window at: %s', newLoc)
- // window.open(newLoc);
- // } else {
- // if (self.fleet.id == 'wag-fleet') {
- // var newLoc = window.location.origin + window.location.pathname + '?config=' + params.config + '#/wag';
- // window.open(newLoc);
- // }
- // window.open('#/dashboard/' + $stateParams.id + '/' + index);
- // }
- // };
- // self.openConsole = function(index) {
- // console.log(self);
- // var params = require('utils/rw.js').getSearchParams(window.location);
- // if (params.api_server) {
- // if (self.fleet.api == "10.0.117.22") self.fleet.api = "10.0.117.17";
- // var newLoc = self.apiServer + '/api/environments/' + self.fleet.id + '/console';
- // window.open(newLoc);
- // } else {
- // window.open('#/dashboard/' + $stateParams.id + '/' + index);
- // }
- // };
-
-
- // self.noisyToggle = function() {
- // var action;
- // if (self.isNoisy) {
- // action = 'stop';
- // self.isNoisy = false;
- // } else {
- // action = 'start';
- // self.isNoisy = true;
- // }
- // $.ajax({
- // url: "http://" + self.fleet.api + ':5050/api/operations/' + action + '-stream',
- // type: "POST",
- // headers: {
- // "Content-Type": "application/vnd.yang.operation+json"
- // },
- // dataType: "json",
- // data: JSON.stringify({
- // "input": {
- // "now": ""
- // }
- // })
- // });
- // };
-
- // self.toggleStatus = function() {
- // var state;
- // var status = self.fleet.status;
- // console.log(self.fleet)
- // if (status == "starting" || status == "stopping") {
- // return;
- // }
- // if (status == "active") {
- // state = "inactive";
- // } else {
- // state = "active";
- // }
- // self.fleet.status = state;
- // FleetStore.setFleetState(self.fleet.id, state);
- // };
- // //CAT + NOISY NEIGHBOR ONLY
-
- // self.catStarted = false;
- // self.catToggle = function() {
- // var action;
- // if (self.catStarted) {
- // action = 'stop';
- // self.catStarted = false;
- // } else {
- // action = 'start';
- // self.catStarted = true;
- // }
- // $.ajax({
- // url: "http://" + self.fleet.api + ':5050/api/operations/' + action + '-cat',
- // type: "POST",
- // headers: {
- // "Content-Type": "application/vnd.yang.operation+json"
- // },
- // dataType: "json",
- // data: JSON.stringify({
- // "input": {
- // "now": ""
- // }
- // })
- // });
- // };
-
- // self.deleteFleet = function() {
- // if (confirm("Do you really want to delete this fleet?")) {
- // // if (confirm("Seriously, you REALLY want to delete this?")) {
- // // fleetChannel.request('fleet:delete', self.fleet);
- // FleetStore.deleteFleet(self.fleet.id)
- // // }
- // }
- // };
-
-
- // /**
- // * WAG Code Start
- // **/
- // // var wagChannel = $rw.radio.channel('wag');
-
- // //If Wag page Offline
- // if (!self.apiServer && self.fleet.id == 'wag-fleet') {
- // self.offline = setInterval(function() {
- // if (self.fleet.started) {
- // self.fleet.wagpage.clientsim['traffic-gen']['rx-pps'] = 100 * (Math.random() - .5) + 200;
- // self.fleet.wagpage.clientsim['traffic-gen']['tx-pps'] = 100 * (Math.random() - .5) + 200;
- // }
- // }, 2000);
- // }
-
- // // If Wag page Online
- // if (self.fleet.template_name == 'Wireless Access Gateway') {
- // wagChannel.command('wag-poll');
- // wagChannel.on('wag-update', function(data) {
- // $timeout(function() {
- // self.fleet.wagpage = {};
- // self.fleet.wagpage.clientsim = data.clientsim
- // // self.data.clientsim['traffic-sink']['rx-pps'] / 1000000;
- // self.isOn = data.clientsim['traffic-gen-status'].running;
- // });
- // });
- // }
-
- // $rootScope.$on('$stateChangeStart', function() {
- // wagChannel.command('wag-poll:kill');
- // });
-
- // /**
- // * WAG Code End
- // **/
-
-
- }
- };
- })
-
-.directive('launchpadFleetCardParams', function() {
- return {
- restrict: 'AE',
- replace: true,
- templateUrl: '/modules/launchpad/launchpad_card/launchpad-fleet-card-params.html',
- controllerAs: 'params',
- scope: {
- $index: "=",
- cardData: '=',
- slaParam: '=?',
- nfviMetric: '=?'
- },
- bindToController: true,
- controller: function($scope, $stateParams, $timeout, $interval, $rootScope) {
- //remove watch
- $scope.$watch(function() {
- return self.cardData;
- }, function() {})
- var self = this;
- var apiServer = require('utils/rw.js').getSearchParams(window.location).api_server;
- self.apiServer = apiServer;
- // self.refs = refsDB;
-
-
- // var LaunchpadFleetActions = require('../launchpadFleetActions.js');
- // var LaunchpadFleetStore = require('../launchpadFleetStore.js');
- //var LaunchpadFleetActions = require('./launchpadFleetActions.js');
-
- self.rate = 40;
- self.packetSize = 256;
- self.refParam = $rw.refParams;
- var currentNFVIRef;
-
- this.visible = 'default';
- if (self.cardData.id == "wag-fleet") {
- self.cardData.started = self.cardData.wagpage.clientsim['traffic-gen-status'].running;
- }
-
-
- self.selectedNFVI = 0;
- self.selectedSLAParam = {};
- self.refPools = $rw.refPools;
-
- // Event Listeners
- // View Actions
- self.load = function(panel) {
- self.visible = panel;
- };
- self.filterSLA = function(ref) {
- // fleetChannel.command('filter:SLAParams', ref);
- FleetActions.filterSLAParams(ref);
- };
- self.filterNFVI = function(ref) {
- FleetActions.filterNfviMetrics(ref)
- // fleetChannel.command('filter:NFVIMetrics', ref);
- };
- self.serviceToggle = function(fleet, control) {
- if (apiServer) {
- var vnfrChannel = $rw.radio.channel('vnfr');
- switch (control.ref) {
- case "trafgen":
- var action = (control.started) ? 'stop' : 'start';
- vnfrChannel.command("vnf:command", action, control.data.api, "http://" + fleet.api + ':5050');
- break;
- case "iot_army":
- var action = (control.started) ? 'stop' : 'start';
- vnfrChannel.command("vnf:command", action, control.data.api, "http://" + fleet.api + ':5050');
- break;
- }
- } else {
- fleetChannel.command('fleet:start', $stateParams.id, self.$index);
- if ($stateParams.id == 'wag-federation') {
- if (!self.cardData.wagpage.clientsim['traffic-gen-status']) {
- self.cardData.wagpage.clientsim['traffic-gen-status'] = {};
- }
- self.cardData.wagpage.clientsim['traffic-gen-status'].running = !self.cardData.wagpage.clientsim['traffic-gen-status'].running;
- }
- }
-
- };
- // Init Params
-
-
- //WAG ONLY
- if (self.apiServer == "http://10.0.201.25:5000") {
- self.isWag = true;
- }
- self.toggleOn = function(e) {
- // self.setPacket(e, self.packetSize);
- // self.setRate(e, self.rate);
- // self.setSubscribers(e, self.subscribers);
- // self.setAP(e, self.ap);
- var wagServer = ""
- if (self.apiServer) {
- if (self.isOn) {
- $.ajax({
- type: "POST",
- url: "http://10.0.201.25:5050/api/running/stop-device-group/",
- success: (function() {
- console.log('stahp post sent')
- })
- });
- $.ajax({
- type: "POST",
- url: "http://10.0.201.25:5050/api/running/stop-sink-server/",
- success: (function() {
- console.log('stahp post sent')
- })
- });
- } else {
- $.ajax({
- type: "POST",
- url: "http://10.0.201.25:5050/api/running/start-sink-server/",
- success: (function() {
- console.log('start post sent')
- })
- });
- $.ajax({
- type: "POST",
- url: "http://10.0.201.25:5050/api/running/start-device-group/",
- success: (function() {
- console.log('start post sent')
- })
- });
- }
- }
- self.isOn = !self.isOn;
- }
-
- //END WAG ONLY
-
- },
- link: function(s, e, a) {}
- }
-});
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
if(true) {
// metricsAndParameters.push(<LaunchpadControls controlSets={this.props.nsr.nsControls} />)
if (this.props.nsr) {
- if (this.props.nsr["nfvi-metrics"]) {
- metricsAndParameters.push((
- <div className="nfvi-metrics" key="nfviMetrics">
- <LpCardNfviMetrics key="nfvi-metrics" name="NFVI METRICS" id={this.props.id} data={this.props.nsr["nfvi-metrics"]} />
- </div>
- ))
- }
+ // if (this.props.nsr["nfvi-metrics"]) {
+ // metricsAndParameters.push((
+ // <div className="nfvi-metrics" key="nfviMetrics">
+ // <LpCardNfviMetrics key="nfvi-metrics" name="NFVI METRICS" id={this.props.id} data={this.props.nsr["nfvi-metrics"]} />
+ // </div>
+ // ))
+ // }
if (this.props.nsr["epa-params"]) {
metricsAndParameters.push(<EpaParams key="epa-params" data={this.props.nsr["epa-params"]} />);
}
html = (
<DashboardCard className={'launchpadCard'} closeCard={closeButton}>
- <LaunchpadHeader nsr={this.props.nsr} name={this.props.name} isActive={this.props.isActive} id={this.props.id}/>
+ <LaunchpadHeader hasAccess={this.props.hasAccess} nsr={this.props.nsr} name={this.props.name} isActive={this.props.isActive} id={this.props.id}/>
{
deleting ?
<div className={'deletingIndicator'}>
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
render() {
let html;
let isDisplayed = this.props.display;
- let status = [];
- if (this.props.nsr['cloud-account']) {
- status.push(
- (<li key="nsr"><h3>NSR: {this.props.nsr['cloud-account']}</h3></li>)
- )
- }
- this.props.nsr['vnfrs'] && this.props.nsr['vnfrs'].map(function(v,i) {
- if(v.hasOwnProperty('cloud-account')) {
- status.push(
- (<li key={i}><h3>VNFR {v['short-name']}: {v['cloud-account']}</h3></li>)
- )
- }
- })
- html = (
- <ul>
- {status}
- </ul>
- )
- return (<div className={this.props.className + (isDisplayed ? '_open':'_close')}><h2>VIM Accounts</h2>{html}</div>);
+ let nsrDataCenter = this.props.nsr['resource-orchestrator'] ? this.props.nsr['resource-orchestrator'] : 'RIFT';
+ return (
+ <div className={this.props.className + (isDisplayed ? '_open':'_close')}>
+ <h2>Accounts</h2>
+ <div className={'dataCenterTable'}>
+ <div className="dataCenterTable-header">
+ <div>TYPE</div>
+ <div>NAME</div>
+ <div>RESOURCE ORCHESTRATOR</div>
+ <div>DATACENTER</div>
+ </div>
+ <div>
+ <div>NSR</div>
+ <div>{this.props.nsr['short-name']}</div>
+ <div>{nsrDataCenter}</div>
+ <div>{this.props.nsr['datacenter']}</div>
+ </div>
+ {
+ this.props.nsr && this.props.nsr['vnfrs'] && this.props.nsr['vnfrs'].map(function(v,i) {
+ if(v.hasOwnProperty('datacenter')) {
+ return <div>
+ <div>VNFR</div>
+ <div>{v['short-name']}</div>
+ <div>{nsrDataCenter}</div>
+ <div>{v['datacenter']}</div>
+ </div>
+ }
+ })
+ }
+ </div>
+ </div>
+ );
}
}
<h3 style={{display: isLoading ? 'none' : 'inherit'}}>
<UpTime initialTime={nsrCreateTime} run={true} />
</h3>
- <h3 className="launchpadCard_header-link" style={{display: this.props.nsr['cloud-account'] ? 'inherit' : 'none'}}>
- <a onClick={this.openCloudAccountPanel} title="VIM Account" className={self.state.displayCloudAccount ? 'activeIcon' : 'inActiveIcon'}>
+ <h3 style={{display: isLoading ? this.props.nsr["operational-status"] == 'vl-init-phase' ? 'inherit' : 'none' : 'none'}}>
+ {this.props.nsr["rw-nsr:orchestration-progress"]["networks"]["active"]} / {this.props.nsr["rw-nsr:orchestration-progress"]["networks"]["total"]}
+ </h3>
+ <h3 style={{display: isLoading ? this.props.nsr["operational-status"] == 'vnf-init-phase' ? 'inherit' : 'none' : 'none'}}>
+ {this.props.nsr["rw-nsr:orchestration-progress"]["vms"]["active"]} / {this.props.nsr["rw-nsr:orchestration-progress"]["vms"]["total"]}
+ </h3>
+ <h3 className="launchpadCard_header-link" style={{display: this.props.nsr['datacenter'] ? 'inherit' : 'none'}}>
+ <a onClick={this.openCloudAccountPanel} title="Datacenters" className={self.state.displayCloudAccount ? 'activeIcon' : 'inActiveIcon'}>
<span className="oi" data-glyph="cloud" aria-hidden="true"></span>
</a>
</h3>
</a>
</h3>
{toggleStatus}
- <h3 className="launchpadCard_header-link" style={{display: 'inherit'}}>
- <a onClick={this.deleteLaunchpad} title="Delete">
- <span className="oi" data-glyph="trash" aria-hidden="true"></span>
- </a>
- </h3>
+ {this.props.hasAccess ?
+ (
+ <h3 className="launchpadCard_header-link" style={{display: 'inherit'}}>
+ <a onClick={this.deleteLaunchpad} title="Delete">
+ <span className="oi" data-glyph="trash" aria-hidden="true"></span>
+ </a>
+ </h3>
+ )
+ : null}
</div>
</div>
<div className="launchpadCard_header-status">
*/
@import 'style/_colors.scss';
@import '../../node_modules/open-iconic/font/css/open-iconic.css';
+$LaunchpadCardWidth: 693px;
.launchpadCard {
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
margin-top: 0.75rem;
&-body{
}
.deletingIndicator {
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
-ms-flex:1;
- flex:1;
+ -webkit-box-flex:1;
+ flex:1;
-ms-flex-align: center;
- align-items: center;
+ -webkit-box-align: center;
+ align-items: center;
-ms-flex-pack: center;
- justify-content: center;
+ -webkit-box-pack: center;
+ justify-content: center;
}
&_launch {
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-direction:column;
- flex-direction:column;
+ -webkit-box-orient:vertical;
+ -webkit-box-direction:normal;
+ flex-direction:column;
-ms-flex: 1;
- flex: 1;
+ -webkit-box-flex: 1;
+ flex: 1;
-ms-flex-pack: center;
- justify-content: center;
+ -webkit-box-pack: center;
+ justify-content: center;
-ms-flex-align: center;
- align-items: center;
+ -webkit-box-align: center;
+ align-items: center;
img {
width: 88px;
padding:1rem;
&_header {
color:white;
padding-right: 1rem;
- min-width: 693px;
- max-width: 693px;
+ width: $LaunchpadCardWidth;
&-link, a {
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-align:center;
- align-items:center;
+ -webkit-box-align:center;
+ align-items:center;
text-decoration: none;
color: inherit;
}
}
&-title {
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
line-height: 3rem;
background-color: $brand-green-light;
/*background-color: $primary-header;*/
position: relative;
-ms-flex-direction: row;
- flex-direction: row;
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ flex-direction: row;
-ms-flex-align: center;
- align-items: center;
+ -webkit-box-align: center;
+ align-items: center;
-ms-flex-pack: start;
- justify-content: flex-start;
+ -webkit-box-pack: start;
+ justify-content: flex-start;
padding-right:1rem;
&-off {
background-color:white;
}
&-actions {
-ms-flex: 1;
- flex: 1;
+ -webkit-box-flex: 1;
+ flex: 1;
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
-ms-flex-pack: end;
- justify-content: flex-end;
+ -webkit-box-pack: end;
+ justify-content: flex-end;
padding-right: 1rem;
h3{
padding-right:0.25rem;
}
&-status {
background-color: $brand-green;
+ width:$LaunchpadCardWidth;
&-current {
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-pack:justify;
- justify-content:space-between;
+ -webkit-box-pack:justify;
+ justify-content:space-between;
padding: 0.5rem 0;
+ width: $LaunchpadCardWidth;
span,a {
padding: 0 0.25rem;
}
&-operational-status {
&_loading {
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-direction:column;
- flex-direction:column;
+ -webkit-box-orient:vertical;
+ -webkit-box-direction:normal;
+ flex-direction:column;
-ms-flex-pack: center;
- justify-content: center;
+ -webkit-box-pack: center;
+ justify-content: center;
justify-content: center;
-ms-flex-align: center;
- align-items: center;
+ -webkit-box-align: center;
+ align-items: center;
color:$gray-darkest;
+ width: $LaunchpadCardWidth;
}
&_open {
overflow: hidden;
width: 100%;
z-index: 99;
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
-ms-flex-direction: column;
- flex-direction: column;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ flex-direction: column;
-ms-flex-align: center;
- align-items: center;
+ -webkit-box-align: center;
+ align-items: center;
-ms-flex-pack: center;
- justify-content: center;
+ -webkit-box-pack: center;
+ justify-content: center;
-ms-flex-line-pack: center;
align-content: center;
+ width: $LaunchpadCardWidth;
color: black;
h2{
padding: 2rem;
&_loading {
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-direction:column;
- flex-direction:column;
+ -webkit-box-orient:vertical;
+ -webkit-box-direction:normal;
+ flex-direction:column;
-ms-flex-pack: center;
- justify-content: center;
+ -webkit-box-pack: center;
+ justify-content: center;
-ms-flex-align: center;
- align-items: center;
+ -webkit-box-align: center;
+ align-items: center;
color:$gray-darkest;
+
+
}
&_open {
overflow:hidden;
width:100%;
z-index: 99;
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
-ms-flex-direction: column;
- flex-direction: column;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ flex-direction: column;
-ms-flex-align: center;
- align-items: center;
- -ms-flex-pack: center;
- justify-content: center;
+ -webkit-box-align: center;
+ align-items: center;
+ justify-content: flex-start;
-ms-flex-line-pack: center;
align-content: center;
color:black;
}
h2 {
padding:2rem;
+ padding-left: 0;
}
ul,li {
margin:0.25rem 1rem 0 0.825rem;
}
li {
-ms-flex-pack: justify;
- justify-content: space-between;
+ -webkit-box-pack: justify;
+ justify-content: space-between;
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
}
}
min-height:50px;
max-width: 100%;
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-pack: center;
- justify-content: center;
+ -webkit-box-pack: center;
+ justify-content: center;
-ms-flex-align: center;
- align-items: center;
+ -webkit-box-align: center;
+ align-items: center;
}
}
&_title {
&_data-list {
ul, dl{
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-direction:row;
- flex-direction:row;
+ -webkit-box-orient:horizontal;
+ -webkit-box-direction:normal;
+ flex-direction:row;
-ms-flex-wrap:wrap;
flex-wrap:wrap;
-ms-flex-align:start;
- align-items:flex-start;
+ -webkit-box-align:start;
+ align-items:flex-start;
font-size: 0.825rem;
li{
-ms-flex:1 1 30%;
- flex:1 1 30%;
+ -webkit-box-flex:1;
+ flex:1 1 30%;
}
}
dl {
}
&_controls{
display:-ms-flexbox;
+ display:-webkit-box;
display:flex;
-ms-flex-direction:row;
- flex-direction:row;
+ -webkit-box-orient:horizontal;
+ -webkit-box-direction:normal;
+ flex-direction:row;
.react-tabs {
-ms-flex: auto;
- flex: auto;
+ -webkit-box-flex: 1;
+ flex: auto;
[role=tab][aria-selected=true] {
background:none;
border:none;
border:none;
&:focus{
outline:none;
- box-shadow:none;
+ -webkit-box-shadow:none;
+ box-shadow:none;
border:none;
&:after {
background:none;
margin: 0 0 10px;
padding: 0;
display: -ms-flexbox;
+ display: -webkit-box;
display: flex;
-ms-flex-pack: end;
- justify-content: flex-end;
+ -webkit-box-pack: end;
+ justify-content: flex-end;
[role=tab]:first-child {
-ms-flex: 1;
- flex: 1;
+ -webkit-box-flex: 1;
+ flex: 1;
}
}
border-radius: 15px;
}
}
+ .dataCenterTable {
+ display: flex;
+ flex-direction: column;
+ width: 88%;
+ &-header {
+ align-items: flex-end;
+ font-weight:bold;
+ }
+ > div {
+ display: flex;
+
+ &:not(:first-child) {
+ margin-top:1rem;
+ align-items: flex-start;
+ }
+ > div {
+ flex: 1 0 25%;
+ }
+ }
+ }
}
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
}
constructConfigPrimitiveTabs = (tabList, tabPanels) => {
let self = this;
+ const hasAccess = self.props.hasAccess;
let defaultFromRpc = '';
//coded here for dev purposes
let mandatoryFieldValue = 'true';
</div>
)
})}
- <Button label="Submit" isLoading={this.state.isSaving} onClick={this.handleExecuteClick.bind(this, nsConfigPrimitiveIndex)} className="dark"/>
+ {
+ hasAccess ?
+ <Button label="Submit" isLoading={this.state.isSaving} onClick={this.handleExecuteClick.bind(this, nsConfigPrimitiveIndex)} className="dark"/>
+ : null
+ }
</TabPanel>
)
);
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
<td>{id}</td>
<td><UpTime initialTime={sgrInstance['create-time']} run={true} /></td>
<td>{sgrInstance['op-status']}</td>
+ <td>{sgrInstance['config-status']}</td>
<td>
{sgrInstance['is-default'] == 'false' ? <a onClick={this.handleDeleteClick.bind(this, this.props.data.id, sgrName, id)} title="Delete">
<span className="oi" data-glyph="trash" aria-hidden="true"></span>
<tr>
<th style={{width: '6%'}}></th>
<th style={{width: '12%'}}>ID</th>
- <th style={{width: '37%'}}>Uptime</th>
- <th style={{width: '37%'}}>Status</th>
+ <th style={{width: '24%'}}>Uptime</th>
+ <th style={{width: '25%'}}>Status</th>
+ <th style={{width: '25%'}}>Config-Status</th>
<th style={{width: '7%'}}> </th>
</tr>
</thead>
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
handleParamChange = (paramIndex, configPrimitiveIndex, vnfrIndex, e) => {
let vnfrs = this.state.vnfrs;
- vnfrs[vnfrIndex]["vnf-configuration"]["service-primitive"][configPrimitiveIndex]["parameter"][paramIndex].value = e.target.value
+ vnfrs[vnfrIndex]["vnf-configuration"]["config-primitive"][configPrimitiveIndex]["parameter"][paramIndex].value = e.target.value
this.setState({
vnfrs: vnfrs
})
}
constructConfigPrimitiveTabs = (tabList, tabPanels) => {
+ const hasAccess = this.props.hasAccess;
let mandatoryFieldValue = 'true';
this.state.vnfrs && this.state.vnfrs.map((vnfr, vnfrIndex) => {
- if (vnfr['vnf-configuration'] && vnfr['vnf-configuration']['service-primitive'] && vnfr['vnf-configuration']['service-primitive'].length > 0) {
- vnfr['vnf-configuration']['service-primitive'].map((configPrimitive, configPrimitiveIndex) => {
+ if (vnfr['vnf-configuration'] && vnfr['vnf-configuration']['config-primitive'] && vnfr['vnf-configuration']['config-primitive'].length > 0) {
+ vnfr['vnf-configuration']['config-primitive'].map((configPrimitive, configPrimitiveIndex) => {
let params = [];
if (configPrimitive['parameter'] && configPrimitive['parameter'].length > 0) {
configPrimitive['parameter'].map((param, paramIndex) => {
{params}
</ul>
</div>
- <button className="dark" role="button" onClick={this.handleExecuteClick.bind(this, configPrimitiveIndex, vnfrIndex)}>{configPrimitive.name}</button>
+ {hasAccess ? <button className="dark" role="button" onClick={this.handleExecuteClick.bind(this, configPrimitiveIndex, vnfrIndex)}>{configPrimitive.name}</button> : null}
</TabPanel>
)
});
}
render() {
-
let tabList = [];
let tabPanels = [];
let isConfiguring = (this.props.data['config-status'] && this.props.data['config-status'] != 'configured') || false;
return (
<div className="nsConfigPrimitives vnfrConfigPrimitives">
<div className="launchpadCard_title">
- SERVICE-PRIMITIVES {displayConfigStatus}
+ CONFIG-PRIMITIVES {displayConfigStatus}
</div>
<div className={isConfiguring ? 'configuring': 'nsConfigPrimitiveTabs'}>
<Tabs onSelect={this.handleSelect}>
//' + String((100/length) - 5) + '%'
switch(node["widget-type"]) {
case 'GAUGE':
- // Disabled for OSM
+ // Disabked fir OSM
// return (
- // <div
- // className="monitoring_params_component"
- // key={prop + '-' + prop.name+ '-' + count}
- // mp={node["mp-id"]}>
- // <div>{node.name}</div>
- // <Components.default.Gauge value={node[valueProperty] || 0} min={numericConstraintsMinPresent ? node['numeric-constraints']['min-value'] : 0} max={numericConstraintsMaxPresent ? node['numeric-constraints']['max-value'] : 100} units={node['units'] || ''} />
- // </div>);
+ // <div
+ // className="monitoring_params_component"
+ // key={prop + '-' + prop.name+ '-' + count}
+ // mp={node["mp-id"]}>
+ // <div>{node.name}</div>
+ // <Components.default.Gauge value={node[valueProperty] || 0} min={numericConstraintsMinPresent ? node['numeric-constraints']['min-value'] : 0} max={numericConstraintsMaxPresent ? node['numeric-constraints']['max-value'] : 100} units={node['units'] || ''} />
+ // </div>);
// break;
case 'TEXTBOX':
case 'COUNTER':
display: flex;
}
.monitoring_params_component {
- flex: 1 1;
+ flex: 1 1;
+ flex-basis: 20%;
text-align:center;
display: flex;
flex-direction: column;
return (
<LaunchpadCard deleting={nsr.deleting}
slideno={this.props.slideno}
- key={index}
+ key={nsr_id}
id={nsr_id}
name={nsr.name}
data={nsr.data}
nsr={nsr}
isActive={nsr["admin-status"] == "ENABLED"}
- closeButtonAction={this.onCloseCard(nsr.id)}/>
+ closeButtonAction={this.onCloseCard(nsr.id)}
+ hasAccess={this.props.hasAccess}/>
);
}
}
import LaunchpadFleetActions from'../launchpadFleetActions';
import LaunchpadFleetStore from '../launchpadFleetStore';
import UpTime from 'widgets/uptime/uptime.jsx';
-
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+const PROJECT_ROLES = ROLES.PROJECT;
/*
* TODO: Handle when page is loading. See recordView for ref
*/
return (
<DashboardCard className="nsListPanel" showHeader={true}
title={title}>
- {this.panelToolbar()}
+ {isRBACValid(this.context.userProfile, [PROJECT_ROLES.LCM_ADMIN, PROJECT_ROLES.PROJECT_ADMIN]) ? this.panelToolbar() : null}
<a onClick={this.handleShowHideToggle(!isVisible)}
className={"nsListPanelToggle"}>
<span className="oi"
}
}
NsListPanel.contextTypes = {
- router: React.PropTypes.object
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
};
NsListPanel.defaultProps = {
isVisible: true
import _forEach from 'lodash/forEach';
import Prism from 'prismjs';
import 'prismjs/themes/prism.css';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
-export default class RecordCard extends React.Component {
+const PROJECT_ROLES = ROLES.PROJECT;
+
+class RecordCard extends React.Component {
constructor(props) {
super(props)
}
let notice = null;
+ let hasAccess = isRBACValid(this.context.userProfile, [PROJECT_ROLES.LCM_ADMIN, PROJECT_ROLES.PROJECT_ADMIN]);
+
switch(this.props.type) {
case 'vnfr' :
cardData = this.props.data[0];
if (displayConfigPrimitives) {
configPrimitiveComponent = (
<div className="flex vnfrConfigPrimitiveContainer">
- <VnfrConfigPrimitives data={configPrimitivesProps} />
+ <VnfrConfigPrimitives data={configPrimitivesProps} hasAccess={hasAccess} />
{/* <NsrPrimitiveJobList jobs={cardData['config-agent-job']}/> */}
<div style={{display:'flex', flexDirection: 'column', flex: '1 1 40%'}}>
<div className="launchpadCard_title">
if (displayConfigPrimitives) {
configPrimitiveComponent = (
<div className="flex nsConfigPrimitiveContainer">
- <NsrConfigPrimitives data={configPrimitivesProps} />
+ <NsrConfigPrimitives data={configPrimitivesProps} hasAccess={hasAccess} />
<div style={{display:'flex', flexDirection: 'column', flex: '1 1 40%'}}>
<div className="launchpadCard_title">
JOB LIST
let metricsAndParams = [];
- let nfviMetrics = <LpCardNfviMetrics data={cardData["nfvi-metrics"]} />;
+ let nfviMetrics = null //<LpCardNfviMetrics data={cardData["nfvi-metrics"]} />;
metricsAndParams.push(<div className="monitoringParams" key="mp">
{components.map(function(c, k) {
return <div key={k} className="mpSlide">{c.title}{c.component}</div>
if (this.props.type == 'nsr') {
primitivesTabTitle = 'Service Primitive';
} else if (this.props.type == 'vnfr') {
- primitivesTabTitle = 'Service Primitive'
+ primitivesTabTitle = 'Config Primitive'
}
tabList.push(
isLoading: true,
jobData: []
}
+RecordCard.contextTypes = {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+};
+
+export default SkyquakeComponent(RecordCard);
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
let nav = <AppHeader nav={navItems} />
if (this.state.showRecordDetails) {
- recordDetails = <RecordDetails isLoading={this.state.detailLoading} data={this.state.rawData} />
+ recordDetails = <RecordDetails isLoading={this.state.detailLoading} data={this.state.rawData} />
}
html = (
<div className="app-body recordView">
remote: function(state, recordID) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + recordID + '?api_server=' + API_SERVER,
+ url: 'api/nsr/' + encodeURIComponent(recordID) + '?api_server=' + API_SERVER,
type: 'GET',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
remote: function(state, vnfrID) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'passthrough/data/api/operational/vnfr-catalog/vnfr/' + vnfrID + '?api_server=' + API_SERVER,
+ url: 'passthrough/data/api/operational/vnfr-catalog/vnfr/' + encodeURIComponent(vnfrID) + '?api_server=' + API_SERVER,
type: 'GET',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
remote: function(state, nsrID) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'passthrough/data/api/operational/ns-instance-opdata/nsr/' + nsrID + '?api_server=' + API_SERVER,
+ url: 'passthrough/data/api/operational/ns-instance-opdata/nsr/' + encodeURIComponent(nsrID) + '?api_server=' + API_SERVER,
type: 'GET',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
return new Promise(function(resolve, reject) {
console.log('Getting NSR Socket');
$.ajax({
- url: '/socket-polling?api_server=' + API_SERVER,
+ url: '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
- url: '/launchpad/api/nsr/' + recordID + '?api_server=' + API_SERVER,
+ url: '/launchpad/api/nsr/' + encodeURIComponent(recordID) + '?api_server=' + API_SERVER,
},
success: function(data) {
Utils.checkAndResolveSocketRequest(data, resolve, reject);
return new Promise(function(resolve, reject) {
console.log('Getting Job Socket');
$.ajax({
- url: '/socket-polling?api_server=' + API_SERVER,
+ url: '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
- url: '/launchpad/api/nsr/' + recordID + '?api_server=' + API_SERVER,
+ url: '/launchpad/api/nsr/' + encodeURIComponent(recordID) + '?api_server=' + API_SERVER,
},
success: function(data) {
Utils.checkAndResolveSocketRequest(data, resolve, reject);
return new Promise(function(resolve, reject) {
console.log('Getting VNFR Socket for: ' + state.recordID);
$.ajax({
- url: '/socket-polling?api_server=' + API_SERVER,
+ url: '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
- url: '/launchpad/api/vnfr/' + state.recordID + '?api_server=' + API_SERVER,
+ url: '/launchpad/api/vnfr/' + encodeURIComponent(state.recordID) + '?api_server=' + API_SERVER,
},
success: function(data) {
Utils.checkAndResolveSocketRequest(data, resolve, reject);
remote(state, params) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + params.nsr_id + '/' + params.scaling_group_id + '/instance' + '?api_server=' + API_SERVER,
+ url: 'api/nsr/' + encodeURIComponent(params.nsr_id) + '/' + encodeURIComponent(params.scaling_group_id) + '/instance' + '?api_server=' + API_SERVER,
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
remote(state, params) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + params.nsr_id + '/' + params.scaling_group_id + '/instance/' + params.scaling_instance_index + '?api_server=' + API_SERVER,
+ url: 'api/nsr/' + encodeURIComponent(params.nsr_id) + '/' + encodeURIComponent(params.scaling_group_id) + '/instance/' + params.scaling_instance_index + '?api_server=' + API_SERVER,
type: 'DELETE',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
remote(state, params) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + params.nsr_id + '/vld' + '?api_server=' + API_SERVER,
+ url: 'api/nsr/' + encodeURIComponent(params.nsr_id) + '/vld' + '?api_server=' + API_SERVER,
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
remote(state, params) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + params.nsr_id + '/vld/' + params.vldId + '?api_server=' + API_SERVER,
+ url: 'api/nsr/' + encodeURIComponent(params.nsr_id) + '/vld/' + encodeURIComponent(params.vldId) + '?api_server=' + API_SERVER,
type: 'DELETE',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
remote(state, params) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + params.nsr_id + '/vld/' + params.vldId + '?api_server=' + API_SERVER,
+ url: 'api/nsr/' + encodeURIComponent(params.nsr_id) + '/vld/' + encodeURIComponent(params.vldId) + '?api_server=' + API_SERVER,
type: 'PUT',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
let configPrimitiveIndex = data.configPrimitiveIndex;
let payload = {};
let isValid = true;
- let configPrimitive = vnfrs[vnfrIndex]['vnf-configuration']['service-primitive'][configPrimitiveIndex];
+ let configPrimitive = vnfrs[vnfrIndex]['vnf-configuration']['config-primitive'][configPrimitiveIndex];
payload['name'] = '';
payload['nsr_id_ref'] = vnfrs[vnfrIndex]['nsr-id-ref'];
return resolve(false);
}
$.ajax({
- url: '/socket-polling?api_server=' + API_SERVER,
+ url: '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
return resolve(false);
}
$.ajax({
- url: '/socket-polling?api_server=' + API_SERVER,
+ url: '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
- url: '/launchpad/api/nsr/' + id + '/compute-topology?api_server=' + API_SERVER
+ url: '/launchpad/api/nsr/' + encodeURIComponent(id) + '/compute-topology?api_server=' + API_SERVER
},
success: function(data, textStatus, jqXHR) {
Utils.checkAndResolveSocketRequest(data, resolve, reject);
id = 0;
return new Promise(function (resolve, reject) {
$.ajax({
- url: '/launchpad/api/nsr/' + id + '/compute-topology?api_server=' + API_SERVER,
+ url: '/launchpad/api/nsr/' + encodeURIComponent(id) + '/compute-topology?api_server=' + API_SERVER,
type: 'GET',
beforeSend: Utils.addAuthorizationStub,
contentType: "application/json",
remote: function(state, vnfrID) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'passthrough/data/api/operational/vnfr-catalog/vnfr/' + vnfrID + '?api_server=' + API_SERVER,
+ url: 'passthrough/data/api/operational/vnfr-catalog/vnfr/' + encodeURIComponent(vnfrID) + '?api_server=' + API_SERVER,
type: 'GET',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
remote: function(state, nsrID) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'passthrough/data/api/operational/ns-instance-opdata/nsr/' + nsrID + '?api_server=' + API_SERVER,
+ url: 'passthrough/data/api/operational/ns-instance-opdata/nsr/' + encodeURIComponent(nsrID) + '?api_server=' + API_SERVER,
type: 'GET',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
remote: function(state, vdurID, vnfrID) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'passthrough/data/api/operational/vnfr-catalog/vnfr/' + vnfrID + '/vdur/' + vdurID + '?api_server=' + API_SERVER,
+ url: 'passthrough/data/api/operational/vnfr-catalog/vnfr/' + encodeURIComponent(vnfrID) + '/vdur/' + encodeURIComponent(vdurID) + '?api_server=' + API_SERVER,
type: 'GET',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
remote(state, nsrId, payload) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + nsrId + '/vld' + '?api_server=' + API_SERVER,
+ url: 'api/nsr/' + encodeURIComponent(nsrId) + '/vld' + '?api_server=' + API_SERVER,
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
dataType:'json',
remote(state, nsrId, vldId) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + nsrId + '/vld/' + vldId + '?api_server=' + API_SERVER,
+ url: 'api/nsr/' + encodeURIComponent(nsrId) + '/vld/' + encodeURIComponent(vldId) + '?api_server=' + API_SERVER,
type: 'DELETE',
beforeSend: Utils.addAuthorizationStub,
success: function(data) {
remote(state, nsrId, vldId, vld) {
return new Promise(function(resolve, reject) {
$.ajax({
- url: 'api/nsr/' + nsrId + '/vld/' + vldId + '?api_server=' + API_SERVER,
+ url: 'api/nsr/' + encodeURIComponent(nsrId) + '/vld/' + encodeURIComponent(vldId) + '?api_server=' + API_SERVER,
type: 'PUT',
beforeSend: Utils.addAuthorizationStub,
dataType:'json',
/*
- *
+ *
* Copyright 2016 RIFT.IO Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
import NSVirtualLinkDetails from './nsVirtualLinkDetails.jsx';
import NSVirtualLinkCreate from './nsVirtualLinkCreate.jsx';
import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import ROLES from 'utils/roleConstants.js';
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
class NsVirtualLinks extends React.Component {
constructor(props) {
super(props);
- this.Store = this.props.flux.stores.hasOwnProperty('NSVirtualLinkCreateStore') ?
+ this.Store = this.props.flux.stores.hasOwnProperty('NSVirtualLinkCreateStore') ?
this.props.flux.stores.NSVirtualLinkCreateStore : this.props.flux.createStore(NSVirtualLinkCreateStore, 'NSVirtualLinkCreateStore');
this.state = {};
this.state.mode = 'viewing'; // Can be 'viewing'/'creating'/'editing'/'deleting'. Default is 'viewing'
if (!this.state.nsd) {
this.setState({
nsd: this.props.data.nsd
- });
+ });
}
if (!this.state.nsrId) {
if (!this.state.nsd) {
this.setState({
nsd: nextProps.data.nsd
- });
+ });
}
if (!this.state.nsrId) {
<tr key={vlrIndex} className={selectedClassName} onClick={this.handleSelectVirtualLinkClick.bind(this, vlrId)}>
<td>{name}</td>
<td>{operationalStatus}</td>
- <td>
- <a onClick={this.handleEditVirtualLinkClick.bind(this, this.props.data.id, vlrId, vldId)}>
- <span className="oi" data-glyph="pencil" aria-hidden="true"></span>
- </a>
- <a onClick={this.handleDeleteVirtualLinkClick.bind(this, this.props.data.id, vldId)}>
- <span className="oi" data-glyph="trash" aria-hidden="true"></span>
- </a>
- </td>
+ {
+ isRBACValid(this.context.userProfile, [PROJECT_ROLES.LCM_ADMIN, PROJECT_ROLES.PROJECT_ADMIN]) ?
+ <td>
+ <a onClick={this.handleEditVirtualLinkClick.bind(this, this.props.data.id, vlrId, vldId)}>
+ <span className="oi" data-glyph="pencil" aria-hidden="true"></span>
+ </a>
+ <a onClick={this.handleDeleteVirtualLinkClick.bind(this, this.props.data.id, vldId)}>
+ <span className="oi" data-glyph="trash" aria-hidden="true"></span>
+ </a>
+ </td>
+ : null
+ }
</tr>
);
});
<tr>
<th style={{width: '50%'}}>Name</th>
<th style={{width: '35%'}}>Status</th>
- <th style={{width: '15%'}}> </th>
+ {
+ isRBACValid(this.context.userProfile, [PROJECT_ROLES.LCM_ADMIN, PROJECT_ROLES.PROJECT_ADMIN]) ?
+ <th style={{width: '15%'}}> </th> : null
+ }
</tr>
</thead>
{tbody}
{nsVirtualLinksTable}
</div>
<div className='nsVirtualLinksCreateButtonWrapper'>
- {nsVirtualLinkCreateButton}
+ { isRBACValid(this.context.userProfile, [PROJECT_ROLES.LCM_ADMIN, PROJECT_ROLES.PROJECT_ADMIN]) ? nsVirtualLinkCreateButton : null}
</div>
</div>
{nsVirtualLinkDetails}
);
}
}
-export default SkyquakeComponent(NsVirtualLinks);
\ No newline at end of file
+export default SkyquakeComponent(NsVirtualLinks);
}
console.log(nsr_id)
$.ajax({
- url: '//' + window.location.hostname + ':' + window.location.port + '/socket-polling?api_server=' + API_SERVER,
+ url: '//' + window.location.hostname + ':' + window.location.port + '/socket-polling',
type: 'POST',
beforeSend: Utils.addAuthorizationStub,
data: {
- url: API_SERVER + ':' + NODE_PORT + '/launchpad/nsr/' + nsr_id + '/vnfr?api_server=' + API_SERVER,
+ url: API_SERVER + ':' + NODE_PORT + '/launchpad/nsr/' + encodeURIComponent(nsr_id) + '/vnfr?api_server=' + API_SERVER,
},
success: function(data) {
Utils.checkAndResolveSocketRequest(data, resolve, reject);
// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
var config = {
devtool: 'source-map',
entry: mainPath,
},
plugins: [
new HtmlWebpackPlugin({
- filename: '../index.html',
- templateContent: '<div id="app"></div>'
+ filename: '../' + htmlFilename,
+ template: frameworkPath + '/plugin-index.html'
})
]
};
function buildGetRequestOptions(req, endpoint) {
var headers = _.extend({},
constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
var api_server = req.query["api_server"];
var requestOptions = {
var headers = _.extend({},
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
var api_server = req.query["api_server"];
var requestOptions = {
var headers = _.extend({},
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
var api_server = req.query["api_server"];
var requestOptions = {
headers: _.extend({},
constants.HTTP_HEADERS.accept.data,
{
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: foreverOn,
rejectUnauthorized: false
* Class to convert RESTConf data to logging plugin
*/
-
+var LoggingConfigDecoder = {};
+var LoggingConfigEncoder = {};
LoggingConfigDecoder = function(debugMode) {
this.debugMode = debugMode || false
"root": "public",
"name": "Logging",
"dashboard": "./loggingGeneral.jsx",
- "order": 101,
+ "order": 3,
"priority":2,
+ "admin_link": true,
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-rbac-platform:platform-admin",
+ "rw-rbac-platform:platform-oper"
+ ],
"routes": [
{
"label": "Logging",
"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",
PLUGIN_NAME=logging
# 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 --optimize-minimize --progress --config webpack.production.config.js
-echo 'Packaging '$PLUGIN_NAME' using webpack... done'
-echo 'Building plugin '$PLUGIN_NAME'... done'
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
#!/bin/bash
-#
+#
# Copyright 2016 RIFT.IO Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
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
+tar -cf $bcache_dir/plugins/$plugin/node_modules.tar node_modules package.json -C $source_dir
#cp -Lrp $source_dir/node_modules $bcache_dir/plugins/$plugin/.
allowDuplicateEvents: loggingConfig.allowDuplicateEvents
},
success: function(data) {
+ if (!loggingConfig.allowDuplicateEvents) {
+ delete state.loggingConfig.allowDuplicateEvents
+ }
resolve(data);
},
error: function(error) {
console.log("LoggingStore.putLoggingConfigSuccess called. data=", data);
const initialLoggingConfig = _cloneDeep(this.loggingConfig);
this.setState({
+ nulledCategories: [],
isLoading: false,
initialLoggingConfig: initialLoggingConfig
});
var CompressionPlugin = require("compression-webpack-plugin");
// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
var config = {
devtool: 'source-map',
entry: mainPath,
},
plugins: [
new HtmlWebpackPlugin({
- filename: '../index.html',
- templateContent: '<div id="app"></div>'
+ filename: '../' + htmlFilename,
+ template: frameworkPath + '/plugin-index.html'
})
]
};
version "3.3.0"
resolved "https://npm.riftio.com:4873/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
+ajv@^4.9.1:
+ version "4.11.8"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
+ dependencies:
+ co "^4.6.0"
+ json-stable-stringify "^1.0.1"
+
align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
resolved "https://npm.riftio.com:4873/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
version "1.5.2"
resolved "https://npm.riftio.com:4873/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-async@~0.2.6:
+async@~0.2.6, async@0.2.x:
version "0.2.10"
resolved "https://npm.riftio.com:4873/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
dependencies:
caniuse-db "^1.0.30000539"
-buffer-shims@^1.0.0:
+buffer-shims@^1.0.0, buffer-shims@~1.0.0:
version "1.0.0"
resolved "https://npm.riftio.com:4873/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
version "0.11.0"
resolved "https://npm.riftio.com:4873/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
+caseless@~0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
+
center-align@^0.1.1:
version "0.1.3"
resolved "https://npm.riftio.com:4873/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
version "1.0.2"
resolved "https://npm.riftio.com:4873/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149"
+co@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+
coa@~1.0.1:
version "1.0.1"
resolved "https://npm.riftio.com:4873/coa/-/coa-1.0.1.tgz#7f959346cfc8719e3f7233cd6852854a7c67d8a3"
dependencies:
delayed-stream "~1.0.0"
-commander@^2.5.0, commander@^2.9.0, commander@2.9.x:
+commander@^2.5.0, commander@^2.8.1, commander@^2.9.0, commander@2.9.x:
version "2.9.0"
resolved "https://npm.riftio.com:4873/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
dependencies:
dependencies:
mime-db ">= 1.24.0 < 2"
+compression-webpack-plugin@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-0.3.2.tgz#1edfb0e749d7366d3e701670c463359b2c0cf704"
+ dependencies:
+ async "0.2.x"
+ webpack-sources "^0.1.0"
+ optionalDependencies:
+ node-zopfli "^2.0.0"
+
compression@^1.5.2:
version "1.6.2"
resolved "https://npm.riftio.com:4873/compression/-/compression-1.6.2.tgz#cceb121ecc9d09c52d7ad0c3350ea93ddd402bc3"
version "0.4.1"
resolved "https://npm.riftio.com:4873/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253"
+defaults@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
+ dependencies:
+ clone "^1.0.2"
+
defined@^1.0.0:
version "1.0.0"
resolved "https://npm.riftio.com:4873/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
nan "^2.3.0"
node-pre-gyp "^0.6.29"
-fstream-ignore@~1.0.5:
+fstream-ignore@^1.0.5, fstream-ignore@~1.0.5:
version "1.0.5"
resolved "https://npm.riftio.com:4873/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
dependencies:
mkdirp ">=0.5 0"
rimraf "2"
+fstream@^1.0.10:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
+ dependencies:
+ graceful-fs "^4.1.2"
+ inherits "~2.0.0"
+ mkdirp ">=0.5 0"
+ rimraf "2"
+
function-bind@^1.0.2:
version "1.1.0"
resolved "https://npm.riftio.com:4873/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
version "1.0.1"
resolved "https://npm.riftio.com:4873/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
+har-schema@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
+
har-validator@~2.0.6:
version "2.0.6"
resolved "https://npm.riftio.com:4873/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
is-my-json-valid "^2.12.4"
pinkie-promise "^2.0.0"
+har-validator@~4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
+ dependencies:
+ ajv "^4.9.1"
+ har-schema "^1.0.5"
+
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://npm.riftio.com:4873/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
version "0.2.3"
resolved "https://npm.riftio.com:4873/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
+json-stable-stringify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
+ dependencies:
+ jsonify "~0.0.0"
+
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://npm.riftio.com:4873/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
version "0.5.1"
resolved "https://npm.riftio.com:4873/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+jsonify@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
+
jsonpointer@^4.0.0:
version "4.0.0"
resolved "https://npm.riftio.com:4873/jsonpointer/-/jsonpointer-4.0.0.tgz#6661e161d2fc445f19f98430231343722e1fcbd5"
version "0.7.2"
resolved "https://npm.riftio.com:4873/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
+nan@^2.0.0:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
+
nan@^2.3.0, nan@^2.3.2:
version "2.4.0"
resolved "https://npm.riftio.com:4873/nan/-/nan-2.4.0.tgz#fb3c59d45fe4effe215f0b890f8adf6eb32d2232"
tar "~2.2.1"
tar-pack "~3.3.0"
+node-pre-gyp@^0.6.4:
+ version "0.6.34"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.34.tgz#94ad1c798a11d7fc67381b50d47f8cc18d9799f7"
+ dependencies:
+ mkdirp "^0.5.1"
+ nopt "^4.0.1"
+ npmlog "^4.0.2"
+ rc "^1.1.7"
+ request "^2.81.0"
+ rimraf "^2.6.1"
+ semver "^5.3.0"
+ tar "^2.2.1"
+ tar-pack "^3.4.0"
+
node-sass@^3.4.2:
version "3.13.1"
resolved "https://npm.riftio.com:4873/node-sass/-/node-sass-3.13.1.tgz#7240fbbff2396304b4223527ed3020589c004fc2"
request "^2.61.0"
sass-graph "^2.1.1"
+node-zopfli@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/node-zopfli/-/node-zopfli-2.0.2.tgz#a7a473ae92aaea85d4c68d45bbf2c944c46116b8"
+ dependencies:
+ commander "^2.8.1"
+ defaults "^1.0.2"
+ nan "^2.0.0"
+ node-pre-gyp "^0.6.4"
+
+nopt@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
+ dependencies:
+ abbrev "1"
+ osenv "^0.1.4"
+
nopt@~3.0.6, "nopt@2 || 3":
version "3.0.6"
resolved "https://npm.riftio.com:4873/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
dependencies:
lodash "^4.17.2"
-npmlog@^4.0.0, npmlog@^4.0.1:
+npmlog@^4.0.0, npmlog@^4.0.1, npmlog@^4.0.2:
version "4.0.2"
resolved "https://npm.riftio.com:4873/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f"
dependencies:
version "1.0.1"
resolved "https://npm.riftio.com:4873/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
-once@^1.3.0:
+once@^1.3.0, once@^1.3.3:
version "1.4.0"
resolved "https://npm.riftio.com:4873/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
version "1.0.2"
resolved "https://npm.riftio.com:4873/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
-osenv@0:
+osenv@^0.1.4, osenv@0:
version "0.1.4"
resolved "https://npm.riftio.com:4873/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
dependencies:
version "2.0.1"
resolved "https://npm.riftio.com:4873/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz#b6e0c8fa99494d94e0511575802a59a5c142f288"
+performance-now@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
+
pify@^2.0.0:
version "2.3.0"
resolved "https://npm.riftio.com:4873/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
version "6.3.0"
resolved "https://npm.riftio.com:4873/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442"
+qs@~6.4.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+
qs@6.2.0:
version "6.2.0"
resolved "https://npm.riftio.com:4873/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b"
version "1.2.0"
resolved "https://npm.riftio.com:4873/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+rc@^1.1.7:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
+ dependencies:
+ deep-extend "~0.4.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
rc@~1.1.6:
version "1.1.6"
resolved "https://npm.riftio.com:4873/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9"
string_decoder "~0.10.x"
util-deprecate "~1.0.1"
+readable-stream@^2.1.4:
+ version "2.2.9"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.9.tgz#cf78ec6f4a6d1eb43d26488cac97f042e74b7fc8"
+ dependencies:
+ buffer-shims "~1.0.0"
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ string_decoder "~1.0.0"
+ util-deprecate "~1.0.1"
+
readable-stream@~2.1.4:
version "2.1.5"
resolved "https://npm.riftio.com:4873/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0"
tunnel-agent "~0.4.1"
uuid "^3.0.0"
+request@^2.81.0:
+ version "2.81.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
+ dependencies:
+ aws-sign2 "~0.6.0"
+ aws4 "^1.2.1"
+ caseless "~0.12.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.0"
+ forever-agent "~0.6.1"
+ form-data "~2.1.1"
+ har-validator "~4.2.1"
+ hawk "~3.1.3"
+ http-signature "~1.1.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.7"
+ oauth-sign "~0.8.1"
+ performance-now "^0.2.0"
+ qs "~6.4.0"
+ safe-buffer "^5.0.1"
+ stringstream "~0.0.4"
+ tough-cookie "~2.3.0"
+ tunnel-agent "^0.6.0"
+ uuid "^3.0.0"
+
require-directory@^2.1.1:
version "2.1.1"
resolved "https://npm.riftio.com:4873/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
dependencies:
align-text "^0.1.1"
+rimraf@^2.5.1, rimraf@^2.6.1:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
+ dependencies:
+ glob "^7.0.5"
+
rimraf@~2.5.1, rimraf@~2.5.4, rimraf@2:
version "2.5.4"
resolved "https://npm.riftio.com:4873/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04"
version "0.2.0"
resolved "https://npm.riftio.com:4873/ripemd160/-/ripemd160-0.2.0.tgz#2bf198bde167cacfa51c0a928e84b68bbe171fce"
+safe-buffer@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
+
sass-graph@^2.1.1:
version "2.1.2"
resolved "https://npm.riftio.com:4873/sass-graph/-/sass-graph-2.1.2.tgz#965104be23e8103cb7e5f710df65935b317da57b"
version "1.1.0"
resolved "https://npm.riftio.com:4873/select/-/select-1.1.0.tgz#a6c520cd9ab919ad81c7d1a273e0452f504dd7a2"
-semver@~5.3.0, "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5":
+semver@^5.3.0, semver@~5.3.0, "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5":
version "5.3.0"
resolved "https://npm.riftio.com:4873/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
dependencies:
amdefine ">=0.0.4"
-source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1:
+source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3:
version "0.5.6"
resolved "https://npm.riftio.com:4873/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
version "0.10.31"
resolved "https://npm.riftio.com:4873/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+string_decoder@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.0.tgz#f06f41157b664d86069f84bdbdc9b0d8ab281667"
+ dependencies:
+ buffer-shims "~1.0.0"
+
string-convert@^0.2.0:
version "0.2.1"
resolved "https://npm.riftio.com:4873/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
version "1.0.4"
resolved "https://npm.riftio.com:4873/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+
style-loader@^0.13.0:
version "0.13.1"
resolved "https://npm.riftio.com:4873/style-loader/-/style-loader-0.13.1.tgz#468280efbc0473023cd3a6cd56e33b5a1d7fc3a9"
version "0.1.10"
resolved "https://npm.riftio.com:4873/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4"
+tar-pack@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984"
+ dependencies:
+ debug "^2.2.0"
+ fstream "^1.0.10"
+ fstream-ignore "^1.0.5"
+ once "^1.3.3"
+ readable-stream "^2.1.4"
+ rimraf "^2.5.1"
+ tar "^2.2.1"
+ uid-number "^0.0.6"
+
tar-pack@~3.3.0:
version "3.3.0"
resolved "https://npm.riftio.com:4873/tar-pack/-/tar-pack-3.3.0.tgz#30931816418f55afc4d21775afdd6720cee45dae"
tar "~2.2.1"
uid-number "~0.0.6"
-tar@^2.0.0, tar@~2.2.1:
+tar@^2.0.0, tar@^2.2.1, tar@~2.2.1:
version "2.2.1"
resolved "https://npm.riftio.com:4873/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
dependencies:
version "0.0.0"
resolved "https://npm.riftio.com:4873/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
+tunnel-agent@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+ dependencies:
+ safe-buffer "^5.0.1"
+
tunnel-agent@~0.4.1:
version "0.4.3"
resolved "https://npm.riftio.com:4873/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
version "1.0.2"
resolved "https://npm.riftio.com:4873/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
-uid-number@~0.0.6:
+uid-number@^0.0.6, uid-number@~0.0.6:
version "0.0.6"
resolved "https://npm.riftio.com:4873/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
supports-color "^3.1.1"
webpack-dev-middleware "^1.4.0"
+webpack-sources@^0.1.0:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.5.tgz#aa1f3abf0f0d74db7111c40e500b84f966640750"
+ dependencies:
+ source-list-map "~0.1.7"
+ source-map "~0.5.3"
+
webpack@^1.3.0:
version "1.14.0"
resolved "https://npm.riftio.com:4873/webpack/-/webpack-1.14.0.tgz#54f1ffb92051a328a5b2057d6ae33c289462c823"
--- /dev/null
+# RIFT_IO_STANDARD_CMAKE_COPYRIGHT_HEADER(BEGIN)
+# Author(s): Kiran Kashalkar
+# Creation Date: 08/18/2015
+# RIFT_IO_STANDARD_CMAKE_COPYRIGHT_HEADER(END)
+
+##
+# DEPENDENCY ALERT
+# The submodule dependencies must be specified in the
+# .gitmodules.dep file at the top level (supermodule) directory
+# If this submodule depends other submodules remember to update
+# the .gitmodules.dep
+##
+
+cmake_minimum_required(VERSION 2.8)
+
+##
+# Submodule specific includes will go here,
+# These are specified here, since these variables are accessed
+# from multiple sub directories. If the variable is subdirectory
+# specific it must be declared in the subdirectory.
+##
+
+rift_externalproject_add(
+ project_management
+ DEPENDS skyquake
+ SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_COMMAND echo
+ BUILD_COMMAND
+ ${CMAKE_CURRENT_BINARY_DIR}/project_management/project_management-build/scripts/build.sh
+ INSTALL_COMMAND
+ ${CMAKE_CURRENT_SOURCE_DIR}/scripts/install.sh
+ ${CMAKE_CURRENT_BINARY_DIR}/project_management/project_management-build
+ ${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+ ${RIFT_SUBMODULE_INSTALL_PREFIX}/skyquake/${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+
+ BCACHE_COMMAND echo
+)
+
--- /dev/null
+{
+ "root": "public",
+ "name": "Project Management",
+ "dashboard": "./dashboard/dashboard.jsx",
+ "order": 1,
+ "priority":2,
+ "admin_link": true,
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-project:project-admin",
+ "rw-project:project-oper"],
+ "routes": [
+ {
+ "label": "Project Management Dashboard",
+ "route": "project-management",
+ "component": "./dashboard/dashboard.jsx",
+ "type": "internal"
+ }]
+}
--- /dev/null
+{
+ "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",
+ "compression-webpack-plugin": "^0.3.2",
+ "cors": "^2.7.1",
+ "css-loader": "^0.23.1",
+ "file-loader": "^0.8.5",
+ "html-webpack-plugin": "^2.9.0",
+ "http-proxy": "^1.12.0",
+ "loaders.css": "^0.1.2",
+ "node-sass": "^3.4.2",
+ "react-addons-css-transition-group": "^0.14.7",
+ "sass-loader": "^3.1.2",
+ "style-loader": "^0.13.0",
+ "webpack": "^1.3.0",
+ "webpack-dev-server": "^1.10.1"
+ }
+}
--- /dev/null
+/*
+ * 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;
--- /dev/null
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+PLUGIN_NAME=project_management
+# change to the directory of this script
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
--- /dev/null
+#!/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/.
--- /dev/null
+/*
+ * 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('/*')
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import ProjectManagementStore from './projectMgmtStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import 'style/layout.scss';
+import './projectMgmt.scss';
+import {Panel, PanelWrapper} from 'widgets/panel/panel';
+import {InputCollection, FormSection} from 'widgets/form_controls/formControls.jsx';
+
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import Input from 'widgets/form_controls/input.jsx';
+import Button, {ButtonGroup} from 'widgets/button/sq-button.jsx';
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import 'widgets/form_controls/formControls.scss';
+import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../node_modules/open-iconic/svg/trash.svg'
+import _ from 'lodash';
+import ROLES from 'utils/roleConstants.js';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
+class ProjectManagementDashboard extends React.Component {
+ constructor(props) {
+ super(props);
+ this.Store = this.props.flux.stores.hasOwnProperty('ProjectManagementStore') ? this.props.flux.stores.ProjectManagementStore : this.props.flux.createStore(ProjectManagementStore);
+ this.Store.getProjects();
+ this.Store.getUsers();
+ this.state = this.Store.getState();
+ this.actions = this.state.actions;
+ }
+ componentDidUpdate() {
+ let self = this;
+ ReactDOM.findDOMNode(this.projectList).addEventListener('transitionend', this.onTransitionEnd, false);
+ setTimeout(function() {
+ let element = self[`project-ref-${self.state.activeIndex}`]
+ element && !isElementInView(element) && element.scrollIntoView({block: 'end', behavior: 'smooth'});
+ })
+ }
+ componentWillMount() {
+ this.Store.listen(this.updateState);
+ }
+ componentWillUnmount() {
+ this.Store.unlisten(this.updateState);
+ }
+ updateState = (state) => {
+ this.setState(state);
+ }
+ updateInput = (key, e) => {
+ let property = key;
+ this.actions.handleUpdateInput({
+ [property]:e.target.value
+ })
+ }
+ disabledChange = (e) => {
+ this.actions.handleDisabledChange(e.target.checked);
+ }
+ platformChange = (platformRole, e) => {
+ this.actions.handlePlatformRoleUpdate(platformRole, e.currentTarget.checked);
+ }
+ addProjectRole = (e) => {
+ this.actions.handleAddProjectItem();
+ }
+ removeProjectRole = (i, e) => {
+ this.actions.handleRemoveProjectItem(i);
+ }
+ updateProjectRole = (i, e) => {
+ this.actions.handleUpdateProjectRole(i, e)
+ }
+ addProject = () => {
+ this.actions.handleAddProject();
+ }
+ viewProject = (un, index) => {
+ this.actions.viewProject(un, index, true);
+ }
+ editProject = () => {
+ this.actions.editProject(false);
+ }
+ cancelEditProject = () => {
+ this.actions.editProject(true)
+ }
+ closePanel = () => {
+ this.actions.handleCloseProjectPanel();
+ }
+
+ deleteProject = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (confirm('Are you sure you want to delete this project?')) {
+ this.Store.deleteProject({
+ 'name': this.state['name']
+ });
+ }
+ }
+ createProject = (e) => {
+ let self = this;
+ e.preventDefault();
+ e.stopPropagation();
+ let projectName = self.state['name'];
+ let projectUsers = self.state.projectUsers;
+ let cleanUsers = this.cleanUsers(projectUsers, projectName);
+
+
+ this.Store.createProject({
+ 'name': projectName,
+ 'description': self.state.description,
+ 'project-config' : {
+ 'user': cleanUsers
+ }
+ });
+ }
+ updateProject = (e) => {
+ let self = this;
+ e.preventDefault();
+ e.stopPropagation();
+ let projectName = self.state['name'];
+ let projectUsers = _.cloneDeep(self.state.projectUsers);
+ let cleanUsers = this.cleanUsers(projectUsers, projectName);
+
+
+ this.Store.updateProject({
+ 'name': projectName,
+ 'description': self.state.description,
+ 'project-config' : {
+ 'user': cleanUsers
+ }
+ });
+ }
+ cleanUsers(projectUsers, projectName) {
+ let self = this;
+ let cleanUsers = [];
+ //Remove null values from role
+ projectUsers.map((u) => {
+ let cleanRoles = [];
+ let cleanManoRoles = [];
+ u.role && u.role.map((r,i) => {
+ let role = {};
+ //you may add a user without a role or a keys, but if one is present then the other must be as well.
+ if(r.role) {
+ delete r.keys;
+ // r.keys = projectName;
+ switch(ROLES.PROJECT.TYPE[r.role]) {
+ case 'rw-project-mano' : cleanManoRoles.push(r); break;
+ case 'rw-project' : cleanRoles.push(r); break;
+ }
+ }
+ });
+ u.role = cleanRoles;
+ u["rw-project-mano:mano-role"] = u["rw-project-mano:mano-role"] || [];
+ u["rw-project-mano:mano-role"] = u["rw-project-mano:mano-role"].concat(cleanManoRoles);
+ //if (u['user-name'] != self.context.userProfile.userId) {
+ cleanUsers.push(u);
+ //}
+ });
+ return cleanUsers;
+ }
+ evaluateSubmit = (e) => {
+ if (e.keyCode == 13) {
+ if (this.props.isEdit) {
+ this.updateProject(e);
+ } else {
+ this.createProject(e);
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+ updateSelectedUser = (e) => {
+ this.setState({
+ selected
+ })
+ }
+ addUserToProject = (e) => {
+ let selectUserList = this.selectUserList;
+ console.log(ReactDOM.findDOMNode(selectUserList))
+ this.actions.handleAddUser(e);
+ }
+ removeUserFromProject = (userIndex, e) => {
+ this.actions.handleRemoveUserFromProject(userIndex);
+ }
+ toggleUserRoleInProject = (userIndex, roleIndex, e) => {
+ this.actions.handleToggleUserRoleInProject({
+ userIndex,
+ roleIndex,
+ checked: JSON.parse(e.currentTarget.checked)
+ })
+ }
+ removeRoleFromUserInProject = (userIndex, roleIndex, e) => {
+ this.actions.handleRemoveRoleFromUserInProject({
+ userIndex,
+ roleIndex
+ })
+ }
+ addRoleToUserInProject = (userIndex, e) => {
+ this.actions.addRoleToUserInProject(userIndex);
+ }
+ onTransitionEnd = (e) => {
+ this.actions.handleHideColumns(e);
+ console.log('transition end')
+ }
+ disableChange = (e) => {
+ let value = e.target.value;
+ value = value.toUpperCase();
+ if (value=="TRUE") {
+ value = true;
+ } else {
+ value = false;
+ }
+ console.log(value)
+ }
+ render() {
+ let self = this;
+ let html;
+ let props = this.props;
+ let state = this.state;
+ let passwordSectionHTML = null;
+ let formButtonsHTML = (
+ <ButtonGroup className="buttonGroup">
+ <Button label="EDIT" type="submit" onClick={this.editProject} />
+ </ButtonGroup>
+ );
+ let projectUsers = [];
+ self.state.projectUsers.map((u) => {
+ projectUsers.push(u);
+ });
+ let availableDomains = state.domains;
+ let availableUsers = state.users && state.users.filter((u) => {
+ return state.selectedDomain == u['user-domain'] && _.findIndex(projectUsers, (s) => {return (s['user-name'] == u['user-name']) && (u['user-domain'] == s['user-domain'])}) == -1
+ }).map((u) => {
+ return {
+ label: `${u['user-name']}`,
+ value: u
+ }
+ });
+
+
+ if(!this.state.isReadOnly) {
+ formButtonsHTML = (
+ state.isEdit ?
+ (
+ <ButtonGroup className="buttonGroup">
+ <Button label="Update" type="submit" onClick={this.updateProject} />
+ <Button label="Delete" onClick={this.deleteProject} />
+ <Button label="Cancel" onClick={this.cancelEditProject} />
+ </ButtonGroup>
+ )
+ : (
+ <ButtonGroup className="buttonGroup">
+ <Button label="Create" type="submit" onClick={this.createProject} />
+ </ButtonGroup>
+ )
+ )
+ }
+
+ html = (
+ <PanelWrapper className={`row projectManagement ${!this.state.projectOpen ? 'projectList-open' : ''}`} style={{'alignContent': 'center', 'flexDirection': 'row'}} >
+ <PanelWrapper ref={(div) => { this.projectList = div}} className={`column projectList expanded ${this.state.projectOpen ? 'collapsed ' : ' '} ${this.state.hideColumns ? 'hideColumns ' : ' '}`}>
+ <Panel title="Project List" style={{marginBottom: 0}} no-corners>
+ <div className="tableRow tableRow--header">
+ <div className="projectName">
+ Project Name
+ </div>
+ <div>
+ Description
+ </div>
+ </div>
+ {state.projects && state.projects.map((u, k) => {
+ let platformRoles = [];
+ for(let role in u.platformRoles) {
+ platformRoles.push(<div>{`${role}: ${u.platformRoles[role]}`}</div>)
+ }
+ return (
+ <div onClick={self.viewProject.bind(null, u, k)} ref={(el) => this[`project-ref-${k}`] = el} className={`tableRow tableRow--data ${((self.state.activeIndex == k) && self.state.projectOpen) ? 'tableRow--data-active' : ''}`} key={k}>
+ <div
+ className={`projectName projectName-header ${((self.state.activeIndex == k) && self.state.projectOpen) ? 'activeProject' : ''}`}
+ >
+ {u['name']}
+ </div>
+ <div>
+ {u['description']}
+ </div>
+
+
+ </div>
+ )
+ })}
+ </Panel>
+ <SkyquakeRBAC className="rbacButtonGroup">
+ <ButtonGroup className="buttonGroup">
+ <Button label="Add Project" onClick={this.addProject} />
+ </ButtonGroup>
+ </SkyquakeRBAC>
+ </PanelWrapper>
+ <PanelWrapper onKeyUp={this.evaluateSubmit}
+ className={`ProjectAdmin column`}>
+ <Panel
+ title={state.isEdit ? state['name'] : 'Create Project'}
+ style={{marginBottom: 0}}
+ hasCloseButton={this.closePanel}
+ no-corners>
+ <FormSection title="PROJECT INFO">
+ {
+ (state.isEditProject || state.isReadOnly) ?
+ <Input readonly={state.isReadOnly || this.state.isEdit} label="Name" value={state['name']} onChange={this.updateInput.bind(null, 'name')} />
+ : null
+ }
+ <Input readonly={state.isReadOnly} type="textarea" label="Description" value={state['description']} onChange={this.updateInput.bind(null, 'description')}></Input>
+ </FormSection>
+ <FormSection title="USER ROLES" className="userTable">
+
+ <table className="projectTable">
+ <thead>
+ <tr>
+ <td>Domain</td>
+ <td>User Name</td>
+ {
+ state.roles.map((r,i) => {
+ return <td key={i}>{r}</td>
+ })
+ }
+ </tr>
+ </thead>
+ <tbody>
+ {
+ state.projectUsers.map((u,i)=> {
+ let userRoles = []
+ u.role && u.role.map((r) => {
+ userRoles.push(r.role);
+ });
+ u["rw-project-mano:mano-role"] && u["rw-project-mano:mano-role"].map((r) => {
+ userRoles.push(r.role);
+ });
+ return (
+ <tr key={i}>
+ <td>
+ {u['user-domain']}
+ </td>
+ <td>
+ {u['user-name']}
+ </td>
+ {
+ state.roles.map((r,j) => {
+ return <td key={j}><Input readonly={state.isReadOnly} type="checkbox" onChange={self.toggleUserRoleInProject.bind(self, i, j)} checked={(userRoles.indexOf(r) > -1)} /></td>
+ })
+ }
+ {
+ !state.isReadOnly ? <td><span
+ className="removeInput"
+ onClick={self.removeUserFromProject.bind(self, i)}
+ >
+ <img src={imgRemove} />
+
+ </span></td> : null
+ }
+
+ </tr>
+ )
+ })
+ }
+ </tbody>
+ </table>
+ <SkyquakeRBAC allow={[PLATFORM.ADMIN, PLATFORM.SUPER]} className="rbacButtonGroup" style={{marginLeft: 0}}>
+ {
+ !state.isReadOnly ?
+ <div className="tableRow tableRow--header">
+ <div>
+ <div className="addUser">
+ {
+ availableDomains.length == 1 ?
+ <SelectOption
+ label="Domain"
+ onChange={this.actions.handleSelectedDomain}
+ defaultValue={state.selectedDomain || availableDomains[0]}
+ initial={false}
+ readonly={true}
+ options={availableDomains}
+ ref={(el) => self.selectUserList = el}
+ /> :
+ <SelectOption
+ label="Domain"
+ onChange={this.actions.handleSelectedDomain}
+ value={state.selectedDomain || availableDomains[0]}
+ initial={false}
+ options={availableDomains}
+ ref={(el) => self.selectUserList = el}
+ />
+ }
+ {
+ availableUsers.length ?
+ <SelectOption
+ label="Username"
+ onChange={this.actions.handleSelectedUser}
+ value={state.selectedUser}
+ initial={true}
+ options={availableUsers}
+ ref={(el) => self.selectUserList = el}
+ /> :
+ <label className="noUsersAvailable">
+ <span>Username</span>
+ <span style={{display: 'block',
+ marginTop: '0.8rem', color: '#666'}}>No Available Users for this Domain</span></label>
+ }
+ {
+ availableUsers.length ?
+ <span className="addInput" onClick={this.addUserToProject}><img src={imgAdd} />
+ Add User
+ </span> :
+ null
+ }
+
+ </div>
+ </div>
+ </div> : null
+ }
+ </SkyquakeRBAC>
+ </FormSection>
+
+ </Panel>
+ <SkyquakeRBAC allow={[PROJECT_ROLES.PROJECT_ADMIN]} project={this.state.name} className="rbacButtonGroup">
+ {formButtonsHTML}
+ </SkyquakeRBAC>
+ </PanelWrapper>
+
+
+ </PanelWrapper>
+ );
+ return html;
+ }
+}
+// onClick={this.Store.update.bind(null, Account)}
+ProjectManagementDashboard.contextTypes = {
+ router: React.PropTypes.object,
+ userProfile: 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/>)
+ }
+}
+
+
+
+
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+@import "style/_colors.scss";
+
+.projectManagement {
+ max-width: 1200px;
+
+ .skyquakePanel-wrapper {
+ overflow-x: hidden;
+ }
+ .projectList {
+
+ -ms-flex: 0 1 200px;
+ -webkit-box-flex: 0;
+ flex: 0 1 200px;
+
+ .activeUser {
+ font-weight:bold;
+ }
+
+ /* transition: all 2s;*/
+ &.expanded {
+ -ms-flex: 1 1 100%;
+ -webkit-box-flex: 1;
+ 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;
+ -webkit-box-flex: 0;
+ 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 {
+ &:not(:first-child) {
+ cursor:pointer;
+ }
+ }
+
+
+ }
+
+ .projectAdmin {
+ -ms-flex: 1 1;
+ -webkit-box-flex: 1;
+ flex: 1 1;
+ width:auto;
+ opacity:1;
+
+ textarea{
+ height: 100px;
+ }
+ }
+ &.projectList-open {
+ .projectAdmin {
+ -ms-flex: 0 1 0px;
+ -webkit-box-flex: 0;
+ flex: 0 1 0px;
+ opacity:0;
+ /* width: 0px;*/
+ display:none;
+ /* transition: opacity 300ms;*/
+ /* transition: width 600ms;*/
+
+ }
+ }
+ .rbacButtonGroup {
+ margin: 0 0.5rem 0.5rem;
+ background: #ddd;
+ padding-bottom: 0.5rem;
+ padding: 0.5rem 0;
+ }
+ .buttonGroup {
+ border-top: #d3d3d3 1px solid;
+ padding-top:0.5rem;
+ }
+ .addUser {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ -ms-flex-direction:row;
+ -webkit-box-orient:horizontal;
+ -webkit-box-direction:normal;
+ flex-direction:row;
+ label {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex: 0 1;
+ -webkit-box-flex: 0;
+ flex: 0 1;
+
+ width:150px;
+ span {
+ margin-bottom: 0.5rem;
+ }
+ span:nth-child(2) {
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ }
+
+ margin-right: 1rem;
+ select {
+ width:150px;
+ }
+ }
+ .noUsersAvailable {
+ display:-webkit-box;
+ display:-ms-flexbox;
+ display:flex;
+ -webkit-box-align: start;
+ -ms-flex-align: start;
+ align-items: flex-start;
+ margin-bottom: 0.75rem;
+ -webkit-box-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ div {
+ width:260px;
+ margin-bottom: 0.5rem;
+ }
+ }
+ }
+ .projectUsers {
+ .userName {
+ -ms-flex-pack: start;
+ -webkit-box-pack: start;
+ justify-content: flex-start;
+ padding-top: 0.75rem;
+ }
+ select {
+ margin-bottom:0.5rem;
+ }
+ .addRole {
+ margin:.25rem 0;
+ }
+ .buttonGroup {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ }
+
+ }
+ .projectUsers.tableRow--data:hover {
+ background:none;
+ color: black;
+ }
+
+ table {
+ font-size: 0.8rem;
+ thead {
+ border-bottom:1px solid #d3d3d3;
+ td{
+ font-weight:bold;
+ }
+ }
+ td{
+ padding:0.25rem 0.5rem;
+ vertical-align: middle;
+ .checkbox {
+ -ms-flex-pack:center;
+ -webkit-box-pack:center;
+ justify-content:center;
+ }
+ }
+ tbody tr {
+ &:nth-child(even) {
+ background:$neutral-dark-0;
+ }
+ }
+ }
+ .userTable {
+ .FormSection-body {
+ overflow-x: auto;
+ }
+ }
+}
+
+
+
+.FormSection {
+ &-title {
+ color: #000;
+ background: lightgray;
+ padding: 0.5rem;
+ border-top: 1px solid #f1f1f1;
+ border-bottom: 1px solid #f1f1f1;
+ }
+ &-body {
+ padding: 0.5rem 0.75rem;
+ }
+ label {
+ -ms-flex: 1 0;
+ -webkit-box-flex: 1;
+ 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:-webkit-box;
+ display:flex;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -ms-flex-align: center;
+ -webkit-box-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:-webkit-box;
+ display:flex;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ padding: 0.25rem;
+ >div {
+ padding: 0.25rem;
+ -ms-flex: 1 1 33%;
+ -webkit-box-flex: 1;
+ flex: 1 1 33%;
+ display: -ms-flexbox;
+ display: -webkit-box;
+ display: flex;
+ -ms-flex-direction: column;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ flex-direction: column;
+ -ms-flex-pack: center;
+ -webkit-box-pack: center;
+ justify-content: center;
+ }
+ &--header {
+ font-weight:bold;
+ }
+ &--data {
+ &:nth-child(even) {
+ background:$neutral-dark-0;
+ }
+ &:hover:not(&-active) {
+ background:$neutral-dark-1;
+ }
+ &:hover, .activeUser, &-active{
+ cursor:pointer;
+ color:white;
+ }
+ .activeUser, &-active{
+ background: #00acee;
+ }
+ }
+}
+
+.addInput, .removeInput {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ -webkit-box-align: end;
+ -ms-flex-align: end;
+ align-items: flex-end;
+ margin-bottom: 0.75rem;
+ 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;
+ }
+}
--- /dev/null
+/*
+ * 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',
+ 'handleSelectedDomain',
+ 'handleUpdateUserRoleInProject',
+ 'handleToggleUserRoleInProject',
+ 'addRoleToUserInProject',
+ 'handleRemoveRoleFromUserInProject',
+ 'updateProjectSuccess',
+ 'createProjectSuccess',
+ 'deleteProjectSuccess'
+ );
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import $ from 'jquery';
+var Utils = require('utils/utils.js');
+let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
+let HOST = API_SERVER;
+let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
+let DEV_MODE = require('utils/rw.js').getSearchParams(window.location).dev_mode || false;
+
+if (DEV_MODE) {
+ HOST = window.location.protocol + '//' + window.location.hostname;
+}
+
+
+
+module.exports = function(Alt) {
+ return {
+
+ getUsers: {
+ remote: function() {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/user?api_server=${API_SERVER}`,
+ type: 'GET',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data.user);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error retrieving the resource orchestrator information.'
+ }),
+ success: Alt.actions.global.getUsersSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ getProjects: {
+ remote: function() {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/project?api_server=${API_SERVER}`,
+ type: 'GET',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data.project);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error retrieving the resource orchestrator information.'
+ }),
+ success: Alt.actions.global.getProjectsSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ updateProject: {
+ remote: function(state, project) {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/project?api_server=${API_SERVER}`,
+ type: 'PUT',
+ data: project,
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error updating the project.'
+ }),
+ success: Alt.actions.global.updateProjectSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ deleteProject: {
+ remote: function(state, project) {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/project/${encodeURIComponent(project['name'])}?api_server=${API_SERVER}`,
+ type: 'DELETE',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error deleting the user.'
+ }),
+ success: Alt.actions.global.deleteProjectSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ createProject: {
+ remote: function(state, project) {
+
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/project?api_server=${API_SERVER}`,
+ type: 'POST',
+ data: project,
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error updating the account.'
+ }),
+ success: Alt.actions.global.createProjectSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ }
+ }
+}
+
+function interceptResponse (responses) {
+ return function(data, action, args) {
+ if(responses.hasOwnProperty(data)) {
+ return {
+ type: data,
+ msg: responses[data]
+ }
+ } else {
+ return data;
+ }
+ }
+}
+
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import ProjectManagementActions from './projectMgmtActions.js';
+import ProjectManagementSource from './projectMgmtSource.js';
+import ROLES from 'utils/roleConstants.js';
+import _ from 'lodash';
+export default class ProjectManagementStore {
+ constructor() {
+ this.actions = ProjectManagementActions(this.alt);
+ this.bindActions(this.actions);
+ this.registerAsync(ProjectManagementSource);
+ this.projects = [];
+ this['name'] = '';
+ this['description'] = 'Some Description';
+ this.projectUsers = [];
+ this.selectedUser = null;
+ this.selectedDomain = null;
+ this.selectedRole = null;
+ this.roles = Object.keys(ROLES.PROJECT).filter((p) => {
+ return p != "TYPE";
+ }).map((p) => {
+ return ROLES.PROJECT[p];
+ })
+ // this.roles = ['rw-project:project-admin', 'rw-project:project-oper', 'rw-project:project-create'];
+ this.users = [];
+ this.domains = [];
+ this.activeIndex = null;
+ this.isReadOnly = true;
+ this.projectOpen = false;
+ this.hideColumns = false;
+ this.isEdit = false;
+ this.isEditProject = true;
+ // 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() {
+ let data = arguments[0];
+ let project = data[0];
+ let projectIndex = data[1];
+ let isReadOnly = data[2];
+
+ let ProjectData = {
+ 'name': project['name'],
+ 'description': project['description'],
+ 'projectUsers': (project['project-config'] && project['project-config']['user'] || [])
+ }
+ let state = _.merge({
+ activeIndex: projectIndex,
+ projectOpen: true,
+ isEdit: true,
+ isReadOnly: isReadOnly,
+ isEditProject: isReadOnly
+ }, ProjectData);
+ this.setState(state)
+ }
+ editProject(isReadOnly) {
+ this.viewProject([this.projects[this.activeIndex], this.activeIndex, isReadOnly]);
+
+ }
+ 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,
+ isEditProject: true,
+ isReadOnly: false,
+ projectUsers: []
+ }
+ ))
+ }
+
+ handleUpdateSelectedUser(user) {
+ this.setState({
+ selectedUser: JSON.parse(user)
+ });
+ }
+
+ handleSelectedDomain(event) {
+ let domain = JSON.parse(event.target.value);
+ this.setState({
+ selectedDomain: domain
+ });
+ }
+ handleAddUser(e) {
+ let self = this;
+ let u = JSON.parse(this.selectedUser);
+ let r = this.selectedRole;
+ let projectUsers = this.projectUsers;
+ console.log('adding user')
+ projectUsers.push({
+ 'user-name': u['user-name'],
+ 'user-domain': u['user-domain'],
+ "role":[{
+ "role": r,
+ "keys": self.name
+ }
+ ]
+ })
+ this.setState({projectUsers, selectedUser: JSON.stringify(null)})
+ }
+ handleToggleUserRoleInProject(data) {
+ let self = this;
+ let {userIndex, roleIndex, checked} = data;
+ let projectUsers = this.projectUsers;
+ let selectedRole = self.roles[roleIndex];
+ let roleType = (ROLES.PROJECT.TYPE[selectedRole] == 'rw-project-mano') ? "rw-project-mano:mano-role" : "role";
+ //
+ if(checked) {
+ if (!projectUsers[userIndex][roleType]) {
+ projectUsers[userIndex][roleType] = [];
+ }
+ projectUsers[userIndex][roleType].push({
+ role: self.roles[roleIndex]
+ })
+ } else {
+ let role = projectUsers[userIndex][roleType];
+ let roleIndex = _.findIndex(role, {role:selectedRole})
+ projectUsers[userIndex][roleType].splice(roleIndex, 1)
+ }
+ self.setState({projectUsers});
+
+ }
+ handleUpdateUserRoleInProject(data) {
+ let {userIndex, roleIndex, value} = data;
+ let projectUsers = this.projectUsers;
+ projectUsers[userIndex].role[roleIndex].role = value;
+
+ }
+ addRoleToUserInProject(userIndex) {
+ let projectUsers = this.projectUsers;
+ if(!projectUsers[userIndex].role) {
+ projectUsers[userIndex].role = [];
+ }
+ projectUsers[userIndex].role.push({
+ 'role': null
+ });
+ this.setState({
+ projectUsers
+ })
+ }
+ handleRemoveRoleFromUserInProject (data) {
+ let {userIndex, roleIndex} = data;
+ let projectUsers = this.projectUsers;
+ projectUsers[userIndex].role.splice(roleIndex, 1);
+ this.setState({
+ projectUsers
+ })
+ }
+ handleRemoveUserFromProject (userIndex) {
+ let projectUsers = this.projectUsers;
+ projectUsers.splice(userIndex, 1);
+ this.setState({
+ projectUsers
+ })
+ }
+ getProjectsSuccess(projects) {
+ this.alt.actions.global.hideScreenLoader.defer();
+ this.setState({projects: projects});
+ }
+ getUsersSuccess(users) {
+ console.log(users)
+ this.alt.actions.global.hideScreenLoader.defer();
+ let domains = users && users.reduce(function(arr, u) {
+ if (arr.indexOf(u['user-domain']) == -1) {
+ arr.push(u['user-domain']);
+ return arr;
+ } else {
+ return arr
+ }
+ }, []);
+ this.setState({users, domains, selectedDomain: domains[0]});
+ }
+ updateProjectSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let self = this;
+ let projects = this.projects || [];
+ projects[this.activeIndex] = {
+ 'name': this['name'],
+ 'description': this['description'],
+ 'project-config': {
+ 'user': self.projectUsers
+ }
+ }
+ this.setState({
+ projects,
+ isEdit: true,
+ isReadOnly: true
+ });
+ }
+ deleteProjectSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let projects = this.projects;
+ projects.splice(this.activeIndex, 1);
+ this.setState({projects, projectOpen: false,isEdit: true,
+ isReadOnly: false,})
+ }
+ createProjectSuccess() {
+ let self = this;
+ this.alt.actions.global.hideScreenLoader.defer();
+ let projects = this.projects || [];
+ projects.push({
+ 'name': self['name'],
+ 'description': self['description'],
+ 'project-config': {
+ 'user': self.projectUsers
+ }
+ });
+ let newState = {
+ projects,
+ isEdit: true,
+ isReadOnly: true,
+ activeIndex: projects.length - 1
+ };
+ _.merge(newState)
+ this.setState(newState);
+ }
+}
--- /dev/null
+/*
+ * 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);
+
+
--- /dev/null
+/*
+ * 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 CompressionPlugin = require("compression-webpack-plugin");
+
+// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
+process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
+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: '../' + htmlFilename,
+ template: frameworkPath + '/plugin-index.html'
+ }),
+ ]
+};
+
+if (process.argv.indexOf('--optimize-minimize') !== -1) {
+ // we are going to output a gzip file in the production process
+ config.output.filename = "gzip-" + config.output.filename;
+ config.plugins.push(new webpack.DefinePlugin({ // <-- key to reducing React's size
+ 'process.env': {
+ 'NODE_ENV': JSON.stringify('production')
+ }
+ }));
+ config.plugins.push(new CompressionPlugin({
+ asset: "[path]", // overwrite js file with gz file
+ algorithm: "gzip",
+ test: /\.(js)$/
+ }));
+}
+
+module.exports = config;
--- /dev/null
+# 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(
+ redundancy
+ DEPENDS skyquake
+ SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_COMMAND echo
+ BUILD_COMMAND
+ ${CMAKE_CURRENT_BINARY_DIR}/redundancy/redundancy-build/scripts/build.sh
+ INSTALL_COMMAND
+ ${CMAKE_CURRENT_SOURCE_DIR}/scripts/install.sh
+ ${CMAKE_CURRENT_BINARY_DIR}/redundancy/redundancy-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
+)
+
--- /dev/null
+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 APIVersion = '/v2'
+var Redundancy = {};
+
+Redundancy.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/operational/redundancy-config';
+
+ _.extend(
+ requestHeaders,
+ constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }
+ );
+
+ request({
+ url: url + '?deep',
+ type: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false
+ },
+ function(error, response, body) {
+ var data;
+ var objKey = 'rw-redundancy:redundancy-config';
+ //SDN model doesn't follow convention
+ if (utils.validateResponse('Redundancy.get', error, response, body, resolve, reject)) {
+ try {
+ data = JSON.parse(response.body);
+ data = data[objKey]
+ } catch (e) {
+ console.log('Problem with "Redundancy.get"', e);
+ var err = {};
+ err.statusCode = 500;
+ err.errorMessage = {
+ error: 'Problem with "Redundancy.get": ' + e
+ }
+ return reject(err);
+ }
+ return resolve({
+ statusCode: response.statusCode,
+ data: data
+ });
+ };
+ });
+ });
+}
+
+
+Redundancy.getState = 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/operational/redundancy-state';
+ _.extend(
+ requestHeaders,
+ constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session && req.session.authorization
+ }
+ );
+ request({
+ url: url + '?deep',
+ type: 'GET',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false
+ },
+ function(error, response, body) {
+ var data;
+ var objKey = 'rw-redundancy:redundancy-state';
+ //SDN model doesn't follow convention
+ if (utils.validateResponse('Redundancy.getState', error, response, body, resolve, reject)) {
+ try {
+ data = JSON.parse(response.body);
+ data = data[objKey]
+ } catch (e) {
+ console.log('Problem with "Redundancy.getState"', e);
+ var err = {};
+ err.statusCode = 500;
+ err.errorMessage = {
+ error: 'Problem with "Redundancy.getState": ' + e
+ }
+ return reject(err);
+ }
+ return resolve({
+ statusCode: response.statusCode,
+ data: data
+ });
+ };
+ });
+ });
+}
+
+Redundancy.configUpdate = function(req) {
+ var self = this;
+ var id = req.params.id || req.params.name;
+ var api_server = req.query["api_server"];
+ var data = req.body;
+ var requestHeaders = {};
+ var createData = {};
+ var updateTasks = [];
+ var method = 'PUT';
+ if(data.hasOwnProperty('revertive-preference')) {
+ var revertivePreferenceUrl = utils.confdPort(api_server) + '/api/config/redundancy-config/revertive-preference';
+ var revertuvePreferenceData = {
+ 'preferred-site-name': data['revertive-preference']['preferred-site-name']
+ };
+ var revertivePreferencePromise = new Promise(function(resolve, reject) {
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: revertivePreferenceUrl,
+ method: method,
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ json: revertuvePreferenceData,
+ }, function(error, response, body) {
+ if (utils.validateResponse('revertivePreferencePromise.' + method, error, response, body, resolve, reject)) {
+ return resolve({
+ statusCode: response.statusCode,
+ data: JSON.stringify(response.body)
+ });
+ };
+ });
+ });
+ updateTasks.push(revertivePreferencePromise);
+ }
+
+ if(data.hasOwnProperty('geographic-failover-decision')) {
+ var geoFailDecisionConfigUrl = utils.confdPort(api_server) + '/api/config/redundancy-config';
+ var geoFailDecisionConfigData = {'geographic-failover-decision' : data['geographic-failover-decision']};
+ var geoFailDecisionPromise = new Promise(function(resolve, reject) {
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: geoFailDecisionConfigUrl,
+ method: method,
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ json: geoFailDecisionConfigData,
+ }, function(error, response, body) {
+ if (utils.validateResponse('geoFailDecisionPromise.' + method, error, response, body, resolve, reject)) {
+ return resolve({
+ statusCode: response.statusCode,
+ data: JSON.stringify(response.body)
+ });
+ };
+ });
+ });
+ updateTasks.push(geoFailDecisionPromise);
+ }
+
+ if(data.hasOwnProperty('user-credentials')) {
+ var userCredentialsUrl = utils.confdPort(api_server) + '/api/config/redundancy-config';
+ var userCredentialsData = {'user-credentials' : data['user-credentials']};
+ var userCredentialsPromise = new Promise(function(resolve, reject) {
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: userCredentialsUrl,
+ method: method,
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ json: userCredentialsData,
+ }, function(error, response, body) {
+ if (utils.validateResponse('userCredentials.' + method, error, response, body, resolve, reject)) {
+ return resolve({
+ statusCode: response.statusCode,
+ data: JSON.stringify(response.body)
+ });
+ };
+ });
+ });
+ updateTasks.push(userCredentialsPromise);
+ }
+
+ if(data.hasOwnProperty('polling-config')) {
+ var pollingConfigUrl = utils.confdPort(api_server) + '/api/config/redundancy-config/polling-config';
+ var pollingConfigData = data['polling-config'];
+ var revertivePreferencePromise = new Promise(function(resolve, reject) {
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: pollingConfigUrl,
+ method: method,
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ json: pollingConfigData,
+ }, function(error, response, body) {
+ if (utils.validateResponse('pollingConfigPromise.' + method, error, response, body, resolve, reject)) {
+ return resolve({
+ statusCode: response.statusCode,
+ data: JSON.stringify(response.body)
+ });
+ };
+ });
+ });
+ updateTasks.push(revertivePreferencePromise);
+ }
+
+ if(data.hasOwnProperty('dns-ip-fqdn')) {
+ var dnsIpFqdn = data['dns-ip-fqdn'];
+ var dnsIpFqdnUrl = utils.confdPort(api_server) + '/api/config/redundancy-config';
+ if(dnsIpFqdn.trim() != '') {
+ var dnsIpFqdnData = {'dns-ip-fqdn' : dnsIpFqdn}
+ var dnsIpFqdnPromise = new Promise(function(resolve, reject) {
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: dnsIpFqdnUrl,
+ method: method,
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ json: dnsIpFqdnData,
+ }, function(error, response, body) {
+ if (utils.validateResponse('dnsIpFqdnPromise.' + method, error, response, body, resolve, reject)) {
+ return resolve({
+ statusCode: response.statusCode,
+ data: JSON.stringify(response.body)
+ });
+ };
+ });
+ });
+ } else {
+ //Delete config item
+ var dnsIpFqdnPromise = new Promise(function(resolve, reject) {
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: dnsIpFqdnUrl + '/dns-ip-fqdn',
+ method: 'DELETE',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false
+ }, function(error, response, body) {
+ if (utils.validateResponse('dnsIpFqdnDelete.' + method, error, response, body, resolve, reject)) {
+ return resolve({
+ statusCode: response.statusCode,
+ data: JSON.stringify(response.body)
+ });
+ };
+ });
+ });
+ }
+ updateTasks.push(dnsIpFqdnPromise);
+ }
+ return new Promise(function(resolve, reject) {
+ Promise.all(updateTasks).then(function(results) {
+ if(results && results[0]) {
+ resolve({
+ statusCode: results[0].statusCode,
+ data: {}
+ });
+ }
+ })
+ })
+}
+
+
+Redundancy.siteUpdate = function(req) {
+ var self = this;
+ var id = req.params.id || req.params.name;
+ var api_server = req.query["api_server"];
+ var data = req.body;
+ var requestHeaders = {};
+ var createData = {};
+ var url = utils.confdPort(api_server) + '/api/config/redundancy-config/site'
+ var method = 'POST'
+ createData = {};
+ if (!id) {
+ createData = {site: data}
+ } else {
+ method = 'PUT';
+ url += '/' + encodeURIComponent(id);
+ createData['rw-redundancy:site'] = data;
+ }
+
+
+
+ return new Promise(function(resolve, reject) {
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: url,
+ method: method,
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ json: createData,
+ }, function(error, response, body) {
+ if (utils.validateResponse('siteUpdate.' + method, error, response, body, resolve, reject)) {
+ return resolve({
+ statusCode: response.statusCode,
+ data: JSON.stringify(response.body)
+ });
+ };
+ });
+ })
+}
+
+Redundancy.siteDelete = function(req) {
+ var self = this;
+ var id = req.params.id || req.params.name;
+ var api_server = req.query["api_server"];
+ var data = req.body;
+ var requestHeaders = {};
+ var createData = {};
+ var url = utils.confdPort(api_server) + '/api/config/redundancy-config/site/';
+ url += encodeURIComponent(id);
+ return new Promise(function(resolve, reject) {
+ _.extend(requestHeaders,
+ constants.HTTP_HEADERS.accept.data,
+ constants.HTTP_HEADERS.content_type.data, {
+ 'Authorization': req.session && req.session.authorization
+ });
+ request({
+ url: url,
+ method: 'DELETE',
+ headers: requestHeaders,
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: false,
+ }, function(error, response, body) {
+ if (utils.validateResponse( 'siteUpdate.DELETE', error, response, body, resolve, reject)) {
+ return resolve({
+ statusCode: response.statusCode,
+ data: JSON.stringify(response.body)
+ });
+ };
+ });
+ })
+}
+
+module.exports = Redundancy;
--- /dev/null
+{
+ "root": "public",
+ "name": "Redundancy",
+ "dashboard": "./dashboard/sites.jsx",
+ "order": 3,
+ "priority":2,
+ "admin_link": true,
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-rbac-platform:platform-admin",
+ "rw-rbac-platform:platform-oper"
+ ],
+ "routes": [
+ {
+ "label": "Sites",
+ "route": "sites",
+ "component": "./dashboard/sites.jsx",
+ "path": "sites",
+ "type": "internal",
+ "routes": [
+
+ ]
+ },{
+ "label": "Config",
+ "route": "config",
+ "component": "./dashboard/config.jsx",
+ "path": "config",
+ "type": "internal",
+ "routes": [
+
+ ]
+ },{
+ "label": "Status",
+ "route": "status",
+ "component": "./dashboard/status.jsx",
+ "path": "status",
+ "type": "internal",
+ "routes": [
+ ]
+ }]
+}
--- /dev/null
+{
+ "name": "redundancy",
+ "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",
+ "compression-webpack-plugin": "^0.3.2",
+ "cors": "^2.7.1",
+ "css-loader": "^0.23.1",
+ "file-loader": "^0.8.5",
+ "html-webpack-plugin": "^2.9.0",
+ "http-proxy": "^1.12.0",
+ "loaders.css": "^0.1.2",
+ "node-sass": "^3.4.2",
+ "react-addons-css-transition-group": "^0.14.7",
+ "sass-loader": "^3.1.2",
+ "style-loader": "^0.13.0",
+ "webpack": "^1.3.0",
+ "webpack-dev-server": "^1.10.1"
+ }
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+var app = require('express').Router();
+var cors = require('cors');
+var utils = require('../../framework/core/api_utils/utils.js')
+
+var redundancyAPI = require('./api/redundancy.js');
+
+app.get('/config', cors(), function(req, res) {
+ redundancyAPI.get(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+app.get('/state', cors(), function(req, res) {
+ redundancyAPI.getState(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+app.post('/site', cors(), function(req, res) {
+ redundancyAPI.siteUpdate(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+app.put('/config', cors(), function(req, res) {
+ redundancyAPI.configUpdate(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+app.put('/site/:id', cors(), function(req, res) {
+ redundancyAPI.siteUpdate(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+app.delete('/site/:id', cors(), function(req, res) {
+ redundancyAPI.siteDelete(req).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+utils.passThroughConstructor(app);
+
+module.exports = app;
--- /dev/null
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+PLUGIN_NAME=redundancy
+# change to the directory of this script
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
--- /dev/null
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+plugin=redundancy
+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/.
--- /dev/null
+/*
+ * 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('/*')
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import RedundancyStore from './redundancyStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import 'style/layout.scss';
+import {Panel, PanelWrapper} from 'widgets/panel/panel';
+import {InputCollection, FormSection} from 'widgets/form_controls/formControls.jsx';
+
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import Input from 'widgets/form_controls/input.jsx';
+import Button, {ButtonGroup} from 'widgets/button/sq-button.jsx';
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import 'widgets/form_controls/formControls.scss';
+import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../node_modules/open-iconic/svg/trash.svg'
+import _ from 'lodash';
+import ROLES from 'utils/roleConstants.js';
+
+import './redundancy.scss';
+const PLATFORM = ROLES.PLATFORM;
+
+class ConfigDashboard extends React.Component {
+ constructor(props) {
+ super(props);
+ this.Store = this.props.flux.stores.hasOwnProperty('RedundancyStore') ? this.props.flux.stores.RedundancyStore : this.props.flux.createStore(RedundancyStore, 'RedundancyStore');
+ this.state = this.Store.getState();
+ this.actions = this.state.actions;
+ }
+ componentDidUpdate() {
+ }
+ componentWillMount() {
+ this.state = this.Store.getState();
+ this.Store.getRedundancy();
+ this.Store.listen(this.updateState);
+ }
+ componentWillUnmount() {
+ this.Store.unlisten(this.updateState);
+ }
+ updateState = (state) => {
+ this.setState(state);
+ }
+ updateConfigInput = (containerName, key, e) => {
+ let configData = this.state[containerName];
+ if(!configData) {
+ configData = {};
+ }
+ configData[key] = e.target.value;
+ this.actions.handleUpdateConfigInput({[containerName]:configData})
+ }
+ updateDnsIpFqdnConfigInput = (e) => {
+ let value = e.target.value;
+ this.actions.handleUpdateConfigInput({'dns-ip-fqdn':value})
+ }
+ updateConfig = (e) => {
+ let self = this;
+ e.preventDefault();
+ e.stopPropagation();
+ let configData = self.state.configData;
+ let userCredentials = configData['user-credentials'];
+ if (!userCredentials || (userCredentials['username'].trim() == '' ) || (userCredentials['password'].trim() == '' )) {
+ self.props.flux.actions.global.showNotification("Please enter your user credentials");
+ return;
+ }
+ this.Store.updateConfig(configData);
+ }
+ evaluateSubmit = (e) => {
+ if (e.keyCode == 13) {
+ if (this.props.isEdit) {
+ this.updateConfig(e);
+ } else {
+ this.updateConfig(e);
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+ failOverDecisionChange = (e) => {
+ let value = e.target.value;
+ value = value.toUpperCase();
+ this.actions.handleFailOverDecisionChange(value);
+ }
+ render() {
+ let self = this;
+ let html;
+ let props = this.props;
+ let state = this.state;
+ let passwordSectionHTML = null;
+ let configData = state.configData;
+ let formButtonsHTML = (
+ <ButtonGroup className="buttonGroup">
+ <Button label="Update" type="submit" onClick={this.updateConfig} />
+ </ButtonGroup>
+ )
+ let GeoFailoverDecision = this.state.configData['geographic-failover-decision'];
+
+ html = (
+ <PanelWrapper onKeyUp={this.evaluateSubmit}
+ className={`SiteAdmin column`} column>
+ <AppHeader nav={[{ name: 'SITES', onClick: this.context.router.push.bind(this, { pathname: '/sites' }) }, { name: 'CONFIG'}, { name: 'STATUS', onClick: this.context.router.push.bind(this, { pathname: '/status' }) }]} />
+ <PanelWrapper onKeyUp={this.evaluateSubmit}
+ className={`SiteAdmin column`} style={{overflow: 'auto'}} column>
+ <Panel
+ title="GEOGRAPHIC FAILOVER DECISION"
+ style={{flex: `0 0 ${GeoFailoverDecision != "INDIRECT" ? '150px' : '220px'}`}}
+ no-corners>
+ <Input className="userInfo-section"
+ type="radiogroup"
+ onChange={this.failOverDecisionChange}
+ value={GeoFailoverDecision}
+ options={state.failOverDecisionOptions}
+ />
+ {
+ GeoFailoverDecision != "INDIRECT" ? null :
+ <Input type="text" onChange={self.updateDnsIpFqdnConfigInput.bind(self)} label="DNS FQDN/IP Address" value={state.configData['dns-ip-fqdn']} />
+ }
+ </Panel>
+ {
+ GeoFailoverDecision == "DIRECT" ?
+ <Panel
+ title="Preferred Failback Site"
+ style={{flex: '0 0 160px'}}
+ no-corners>
+ <Input type="text" onChange={self.updateConfigInput.bind(self, 'revertive-preference', 'preferred-site-name')} label="Site Name" value={state.configData['revertive-preference'] && state.configData['revertive-preference']['preferred-site-name']} />
+ </Panel>
+ : null
+ }
+ <Panel
+ title="User Credentials"
+ style={{flex: '0 0 250px'}}
+ no-corners>
+ <Input type="text" onChange={self.updateConfigInput.bind(self, 'user-credentials', 'username')} label="Username" value={state.configData['user-credentials'] && state.configData['user-credentials']['username']}
+ required />
+ <Input type="password" onChange={self.updateConfigInput.bind(self, 'user-credentials', 'password')} label="Password" value={state.configData['user-credentials'] && state.configData['user-credentials']['password']}
+ required />
+ </Panel>
+ <Panel
+ title="Polling Configuration"
+ style={{flex: `0 0 ${GeoFailoverDecision != "INDIRECT" ? '390px' : '230px'}`}}
+ no-corners>
+ <Input type="text" onChange={self.updateConfigInput.bind(self, 'polling-config', 'poll-interval')} label="polling interval (s)" value={state.configData['polling-config'] && state.configData['polling-config']['poll-interval']} />
+ {
+ GeoFailoverDecision == "DIRECT" ?
+ <Input type="text" onChange={self.updateConfigInput.bind(self, 'polling-config', 'failover-timeo')} label="Failover Timeout (s)" value={state.configData['polling-config'] && state.configData['polling-config']['failover-timeo']} />
+ : null
+ }
+ {
+ GeoFailoverDecision == "DIRECT" ?
+ <Input type="text" onChange={self.updateConfigInput.bind(self, 'polling-config', 'failback-timeo')} label="Failback Timeout (s)" value={state.configData['polling-config'] && state.configData['polling-config']['failback-timeo']} />
+ : null
+ }
+ <Input type="text" onChange={self.updateConfigInput.bind(self,'polling-config', 'no-response-counter')} label="No Response Counter" value={state.configData['polling-config'] && state.configData['polling-config']['no-response-counter']} />
+
+
+ </Panel>
+ </PanelWrapper>
+ <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} site={this.state.name} className="rbacButtonGroup">
+ {formButtonsHTML}
+ </SkyquakeRBAC>
+ </PanelWrapper>
+ );
+ return html;
+ }
+}
+// onClick={this.Store.update.bind(null, Account)}
+ConfigDashboard.contextTypes = {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+};
+
+ConfigDashboard.defaultProps = {
+ siteList: [],
+ selectedSite: {}
+}
+
+export default SkyquakeComponent(ConfigDashboard);
+
+
+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/>)
+ }
+}
+
+function showInput(e){
+ let target = e.target;
+ if(target.parentElement.classList.contains("addInput")) {
+ target = target.parentElement;
+ }
+ target.style.display = 'none';
+ target.parentElement.nextElementSibling.style.display = 'flex';
+ // e.target.parentElement.nextElementSibling.children[1].style.display = 'initial';
+}
+function hideInput(e){
+ let target = e.target;
+ if(target.parentElement.classList.contains("removeInput")) {
+ target = target.parentElement;
+ }
+ target.parentElement.style.display = 'none';
+ target.parentElement.previousElementSibling.children[1].style.display = 'inline';
+ target.previousSibling.value = '';
+}
+
+
--- /dev/null
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react';
+import AppHeader from 'widgets/header/header.jsx';
+import RedundancyStore from './redundancyStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import ROLES from 'utils/roleConstants.js';
+
+
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
+//Delete this line after testing is done
+// PROJECT_ROLES.ACCOUNT_ADMIN = '';
+import 'style/layout.scss';
+
+class AccountsDashboard extends React.Component {
+ constructor(props) {
+ super(props);
+ this.Store = this.props.flux.stores.hasOwnProperty('RedundancyStore') ? this.props.flux.stores.RedundancyStore : this.props.flux.createStore(RedundancyStore, "RedundancyStore");
+ this.state = this.Store.getState();
+ }
+ componentWillMount() {
+ this.Store.listen(this.updateState);
+ }
+ componentWillUnmount() {
+ this.Store.unlisten(this.updateState);
+ }
+ updateState = (state) => {
+ this.setState(state);
+ }
+ render() {
+ let self = this;
+ let html;
+ let READONLY = !isRBACValid(this.context.userProfile, [PROJECT_ROLES.ACCOUNT_ADMIN, PROJECT_ROLES.PROJECT_ADMIN]);
+ html = (<div className="launchpad-account-dashboard content-wrapper">
+ <AppHeader nav={[{ name: 'SITES', onClick: this.context.router.push.bind(this, { pathname: '/sites' }) }, { name: 'CONFIG', onClick: this.context.router.push.bind(this, { pathname: '/config' }) }, { name: 'STATUS', onClick: this.context.router.push.bind(this, { pathname: '/status' }) }]} />
+ <div className="flex">
+ <div>
+ { this.props.children ? React.cloneElement(this.props.children, {readonly: READONLY, store: self.Store, ...self.state}) : 'Edit or Create New Accounts'
+ }
+ </div>
+ </div>
+ </div>);
+ return html;
+ }
+}
+AccountsDashboard.contextTypes = {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+};
+
+export default SkyquakeComponent(AccountsDashboard);
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+@import "style/_colors.scss";
+
+.siteManagement {
+ max-width: 1200px;
+
+ .skyquakePanel-wrapper {
+ overflow-x: hidden;
+ }
+ .siteList {
+
+ -ms-flex: 0 1 200px;
+ -webkit-box-flex: 0;
+ flex: 0 1 200px;
+
+ .activeUser {
+ font-weight:bold;
+ }
+
+ /* transition: all 2s;*/
+ &.expanded {
+ -ms-flex: 1 1 100%;
+ -webkit-box-flex: 1;
+ flex: 1 1 100%;
+ /* transition: all 300ms;*/
+ .tableRow>div:not(.siteName) {
+ opacity: 1;
+ /* width:auto;*/
+ /* transition: width 600ms;*/
+ /* transition: opacity 300ms;*/
+ }
+ &.collapsed {
+ -ms-flex: 0 1 200px;
+ -webkit-box-flex: 0;
+ flex: 0 1 200px;
+ /* transition: all 2s;*/
+ .tableRow>div:not(.siteName) {
+ /* opacity: 0;*/
+ /* width:0px;*/
+ display:none;
+ overflow:hidden;
+ /* transition: all 600ms;*/
+ }
+ }
+ }
+ &.hideColumns {
+ overflow:hidden;
+ >div {
+ overflow:hidden;
+ }
+ .tableRow>div:not(.siteName) {
+ width: 0px;
+ /* transition: all 600ms;*/
+ }
+ .siteName {
+ &--header {
+ /* display:none;*/
+ }
+ }
+ }
+ .siteName {
+ &:not(:first-child) {
+ cursor:pointer;
+ }
+ }
+
+
+ }
+
+ .siteAdmin {
+ -ms-flex: 1 1;
+ -webkit-box-flex: 1;
+ flex: 1 1;
+ width:auto;
+ opacity:1;
+
+ textarea{
+ height: 100px;
+ }
+ }
+ &.siteList-open {
+ .siteAdmin {
+ -ms-flex: 0 1 0px;
+ -webkit-box-flex: 0;
+ flex: 0 1 0px;
+ opacity:0;
+ /* width: 0px;*/
+ display:none;
+ /* transition: opacity 300ms;*/
+ /* transition: width 600ms;*/
+
+ }
+ }
+ .rbacButtonGroup {
+ margin: 0 0.5rem 0.5rem;
+ background: #ddd;
+ padding-bottom: 0.5rem;
+ padding: 0.5rem 0;
+ }
+ .buttonGroup {
+ border-top: #d3d3d3 1px solid;
+ padding-top:0.5rem;
+ }
+ .addUser {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ -ms-flex-direction:row;
+ -webkit-box-orient:horizontal;
+ -webkit-box-direction:normal;
+ flex-direction:row;
+ label {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex: 0 1;
+ -webkit-box-flex: 0;
+ flex: 0 1;
+
+ width:150px;
+ span {
+ margin-bottom: 0.5rem;
+ }
+ span:nth-child(2) {
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ }
+
+ margin-right: 1rem;
+ select {
+ width:150px;
+ }
+ }
+ .noUsersAvailable {
+ display:-webkit-box;
+ display:-ms-flexbox;
+ display:flex;
+ -webkit-box-align: start;
+ -ms-flex-align: start;
+ align-items: flex-start;
+ margin-bottom: 0.75rem;
+ -webkit-box-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ div {
+ width:260px;
+ margin-bottom: 0.5rem;
+ }
+ }
+ }
+ .siteUsers {
+ .userName {
+ -ms-flex-pack: start;
+ -webkit-box-pack: start;
+ justify-content: flex-start;
+ padding-top: 0.75rem;
+ }
+ select {
+ margin-bottom:0.5rem;
+ }
+ .addRole {
+ margin:.25rem 0;
+ }
+ .buttonGroup {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ }
+
+ }
+ .siteUsers.tableRow--data:hover {
+ background:none;
+ color: black;
+ }
+
+ table {
+ font-size: 0.8rem;
+ thead {
+ border-bottom:1px solid #d3d3d3;
+ td{
+ font-weight:bold;
+ }
+ }
+ td{
+ padding:0.25rem 0.5rem;
+ vertical-align: middle;
+ .checkbox {
+ -ms-flex-pack:center;
+ -webkit-box-pack:center;
+ justify-content:center;
+ }
+ }
+ tbody tr {
+ &:nth-child(even) {
+ background:$neutral-dark-0;
+ }
+ }
+ }
+ .userTable {
+ .FormSection-body {
+ overflow-x: auto;
+ }
+ }
+
+
+ .FormSection {
+ &-title, &-body {
+ padding-left: 0px;
+ }
+ }
+ .subSection {
+ .FormSection-title {
+ color: #000;
+ background: none;
+ padding: 0.5rem;
+ border-top: none;
+ border-bottom: 1px solid $neutral-dark-2;
+ text-transform:uppercase;
+ width: 50%;
+ padding-left: 0;
+
+ }
+ .FormSection-body {
+ padding-left: 0;
+ }
+ label {
+ -ms-flex: 1 0;
+ -webkit-box-flex: 1;
+ 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;
+ }
+ }
+ .sqCheckBox {
+ width: 480px;
+ }
+}
+
+
+
+.InputCollection {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -ms-flex-align: center;
+ -webkit-box-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:-webkit-box;
+ display:flex;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ padding: 0.25rem;
+ >div {
+ padding: 0.25rem;
+ -ms-flex: 1 1 33%;
+ -webkit-box-flex: 1;
+ flex: 1 1 33%;
+ display: -ms-flexbox;
+ display: -webkit-box;
+ display: flex;
+ -ms-flex-direction: column;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ flex-direction: column;
+ -ms-flex-pack: center;
+ -webkit-box-pack: center;
+ justify-content: center;
+ }
+ &--header {
+ font-weight:bold;
+ }
+ &--data {
+ &:nth-child(even) {
+ background:$neutral-dark-0;
+ }
+ &:hover:not(&-active) {
+ background:$neutral-dark-1;
+ }
+ &:hover, .activeUser, &-active{
+ cursor:pointer;
+ color:white;
+ }
+ .activeUser, &-active{
+ background: #00acee;
+ }
+ }
+}
+.rwInstance {
+ width:100%;
+ border-bottom: 1px solid $neutral-dark-5;
+ margin-bottom: 1rem;
+ h3 {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ justify-content: space-between;
+ color: $neutral-dark-5;
+ margin-bottom: 1rem;
+ }
+ span.title {
+ color: $gray-darker;
+ }
+ .sqTextInput {
+ max-width: 400px;
+ }
+}
+
+.sqCheckBox label input {
+ min-width: auto;
+ margin: 1rem;
+}
+.addInput, .removeInput {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ -webkit-box-align: end;
+ -ms-flex-align: end;
+ align-items: flex-end;
+ margin-bottom: 0.75rem;
+ 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;
+ }
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+module.exports = function(Alt) {
+ return Alt.generateActions(
+ 'handleUpdateInput',
+ 'handleAddSiteItem',
+ 'handleRemoveSiteItem',
+ 'handleUpdateSiteRole',
+ 'handleUpdateConfigInput',
+ 'viewSite',
+ 'editSite',
+ 'handleCloseSitePanel',
+ 'handleHideColumns',
+ 'handleSelectedUser',
+ 'handleSelectedRole',
+ 'handleAddUser',
+ 'handleRemoveUserFromSite',
+ 'getSitesSuccess',
+ 'getRedundancySuccess',
+ 'getSitesNotification',
+ 'handleDisabledChange',
+ 'handlePlatformRoleUpdate',
+ 'handleAddSite',
+ 'handleCreateSite',
+ 'handleUpdateSite',
+ 'handleAddTargetEndpoint',
+ 'handleRemoveTargetEndpoint',
+ 'handleAddInstance',
+ 'handleRemoveInstance',
+ 'handleFailOverDecisionChange',
+ 'openRedundancyStateSocketSuccess',
+ 'updateSiteSuccess',
+ 'createSiteSuccess',
+ 'deleteSiteSuccess',
+ 'updateConfigSuccess'
+ );
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import $ from 'jquery';
+var Utils = require('utils/utils.js');
+let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
+let HOST = API_SERVER;
+let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
+let DEV_MODE = require('utils/rw.js').getSearchParams(window.location).dev_mode || false;
+
+if (DEV_MODE) {
+ HOST = window.location.protocol + '//' + window.location.hostname;
+}
+
+
+
+module.exports = function(Alt) {
+ return {
+
+ getRedundancy: {
+ remote: function() {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `config?api_server=${API_SERVER}`,
+ type: 'GET',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error retrieving the redundancy config.'
+ }),
+ success: Alt.actions.global.getRedundancySuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ openRedundancyStateSocket: {
+ remote: function(state) {
+ return new Promise(function(resolve, reject) {
+ //If socket connection already exists, eat the request.
+ if(state.socket) {
+ console.log('connection already exists')
+ return resolve(false);
+ }
+ $.ajax({
+ url: '/socket-polling',
+ type: 'POST',
+ beforeSend: Utils.addAuthorizationStub,
+ data: {
+ url: 'redundancy/state?api_server=' + API_SERVER
+ },
+ success: function(data, textStatus, jqXHR) {
+ Utils.checkAndResolveSocketRequest(data, resolve, reject);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ reject(xhr.responseText || 'An error occurred. Check your logs for more information');
+ });;
+ });
+ },
+ loading: Alt.actions.global.showScreenLoader,
+ success: Alt.actions.global.openRedundancyStateSocketSuccess
+ },
+ getSites: {
+ remote: function() {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `site?api_server=${API_SERVER}`,
+ type: 'GET',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data.site);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error retrieving the redundancy sites.'
+ }),
+ success: Alt.actions.global.getSitesSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ updateConfig: {
+ remote: function(state, site) {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `config?api_server=${API_SERVER}`,
+ type: 'PUT',
+ data: site,
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error updating the redundancy config.'
+ }),
+ success: Alt.actions.global.updateConfigSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ updateSite: {
+ remote: function(state, site) {
+ let data = site;
+ data['rw-instances'].map(function(s) {
+ if(s.hasOwnProperty('isNew')) {
+ delete s.isNew;
+ }
+ })
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `site/${encodeURIComponent(site['site-name'])}?api_server=${API_SERVER}`,
+ type: 'PUT',
+ data: data,
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error updating the site.'
+ }),
+ success: Alt.actions.global.updateSiteSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ deleteSite: {
+ remote: function(state, site) {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `site/${encodeURIComponent(site['site-name'])}?api_server=${API_SERVER}`,
+ type: 'DELETE',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error deleting the site.'
+ }),
+ success: Alt.actions.global.deleteSiteSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ createSite: {
+ remote: function(state, site) {
+ let data = site;
+ data['rw-instances'].map(function(s) {
+ if(s.hasOwnProperty('isNew')) {
+ delete s.isNew;
+ }
+ })
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `site?api_server=${API_SERVER}`,
+ type: 'POST',
+ data: data,
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error creating the site.'
+ }),
+ success: Alt.actions.global.createSiteSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ }
+ }
+}
+
+function interceptResponse (responses) {
+ return function(data, action, args) {
+ if(responses.hasOwnProperty(data)) {
+ return {
+ type: data,
+ msg: responses[data]
+ }
+ } else {
+ return data;
+ }
+ }
+}
+
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import RedundancyActions from './redundancyActions.js';
+import RedundancySource from './redundancySource.js';
+var Utils = require('utils/utils.js');
+import ROLES from 'utils/roleConstants.js';
+import _ from 'lodash';
+export default class RedundancyStore {
+ constructor() {
+ this.actions = RedundancyActions(this.alt);
+ this.bindActions(this.actions);
+ this.registerAsync(RedundancySource);
+ this.sites = [];
+ this.failoverDecision = "INDIRECT";
+ this.siteData = {
+ 'target-endpoint': [],
+ 'rw-instances':[{
+ endpoint:[{},{}]
+ }]
+
+ };
+ this.configData = {
+ 'polling-config' : {},
+ 'revertive-preference': {},
+ 'geographic-failover-decision': 'INDIRECT',
+ 'user-credentials': {
+ 'username': '',
+ 'password': ''
+ }
+ }
+ this.siteIdPattern = /^((((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))(%[\p{N}\p{L}]+)?)|(^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$))|(((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)|\.)$/;
+ this.siteIdValidation = [ "^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$"
+ , "((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))(%[\p{N}\p{L}]+)?"
+ , '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)|\.'
+ ];
+ this.failOverDecisionOptions = [{ label: "INDIRECT", value: "INDIRECT" }, { label: "DIRECT", value: "DIRECT" }];
+ this.status = {};
+ this.activeIndex = null;
+ this.isReadOnly = true;
+ this.siteOpen = false;
+ this.hideColumns = false;
+ this.isEdit = false;
+ this.isEditSite = true;
+ this.exportPublicMethods({
+ closeSocket: this.closeSocket
+ })
+ }
+ /**
+ * [handleFieldUpdate description]
+ * @param {Object} data {
+ * [store_property] : [value]
+ * }
+ * @return {[type]} [description]
+ */
+ handleUpdateInput(data) {
+ this.setState(data);
+ }
+ handleUpdateConfigInput(data) {
+ let configData = this.configData;
+ configData = _.merge(configData, data);
+ this.setState(configData);
+ }
+ handleFailOverDecisionChange(failoverDecision) {
+ let configData = this.configData;
+ configData['geographic-failover-decision'] = failoverDecision
+ delete configData['dns-ip-fqdn'];
+ this.setState({configData});
+ }
+
+ viewSite() {
+ let self = this;
+ let data = arguments[0];
+ let SiteData = data[0];
+ let siteIndex = data[1];
+ let isReadOnly = data[2];
+
+ let state = _.merge({
+ activeIndex: siteIndex,
+ siteOpen: true,
+ isEdit: true,
+ isReadOnly: isReadOnly,
+ isEditSite: isReadOnly,
+ siteData: SiteData
+ });
+ this.setState(state)
+ }
+ editSite(isReadOnly) {
+ this.viewSite([this.sites[this.activeIndex], this.activeIndex, isReadOnly]);
+
+ }
+ handleCloseSitePanel() {
+ this.setState({
+ siteOpen: false,
+ isEdit: false,
+ isReadOnly: true
+ })
+ }
+ handleHideColumns(e) {
+ if(this.siteOpen && e.currentTarget.classList.contains('hideColumns')) {
+ this.setState({
+ hideColumns: true
+ })
+ } else {
+ this.setState({
+ hideColumns: false
+ })
+ }
+ }
+ resetSite() {
+ let name = '';
+ let description = '';
+ return {
+ siteData: {
+ 'target-endpoint': [{},{}],
+ 'rw-instances':[{
+ isNew: true,
+ endpoint:[{},{}]
+ }]
+ }
+ }
+ }
+ handleAddSite() {
+ this.setState(_.merge( this.resetSite() ,
+ {
+ isEdit: false,
+ siteOpen: true,
+ activeIndex: null,
+ isEditSite: true,
+ isReadOnly: false,
+ }
+ ))
+ }
+ handleAddTargetEndpoint() {
+ let newSiteData = this.siteData;
+ if(!newSiteData['target-endpoint']) {
+ newSiteData['target-endpoint'] = [];
+ }
+ newSiteData['target-endpoint'].push({
+ name: '',
+ port: ''
+ })
+ this.setState({siteData: newSiteData})
+ }
+ handleRemoveTargetEndpoint(data) {
+ let newSiteData = this.siteData;
+ newSiteData['target-endpoint'].splice(
+ data[0].index
+ , 1
+ );
+ this.setState({siteData: newSiteData})
+ }
+ handleAddInstance() {
+ let newSiteData = this.siteData;
+ if(!newSiteData['rw-instances']) {
+ newSiteData['rw-instances'] = [];
+ }
+ newSiteData['rw-instances'].push({
+ isNew: true,
+ endpoint:[{},{}]
+ })
+ this.setState({siteData: newSiteData})
+ }
+ handleRemoveInstance(data) {
+ let newSiteData = this.siteData;
+ newSiteData['rw-instances'].splice(
+ data[0].index
+ , 1
+ );
+ this.setState({siteData: newSiteData})
+ }
+ getSitesSuccess(sites) {
+ this.alt.actions.global.hideScreenLoader.defer();
+ this.setState({sites: sites});
+ }
+ getRedundancySuccess(data) {
+ console.log(data)
+ this.alt.actions.global.hideScreenLoader.defer();
+ let sites = data.site && data.site.map(function(site, i) {
+ return site;
+ });
+ this.setState({
+ sites,
+ configData : {
+ 'polling-config' : data['polling-config'],
+ 'revertive-preference': data['revertive-preference'],
+ 'geographic-failover-decision': data['geographic-failover-decision'],
+ 'dns-ip-fqdn': data['dns-ip-fqdn'],
+ 'user-credentials': data['user-credentials'] || {
+ 'username': '',
+ 'password': ''
+ }
+ }
+ });
+ }
+ updateConfigSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let self = this;
+ this.setState({
+ isEdit: true,
+ isReadOnly: true
+ });
+ }
+ updateSiteSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let self = this;
+ let sites = this.sites || [];
+ sites[this.activeIndex] = this.siteData
+ this.setState({
+ sites,
+ isEdit: true,
+ isReadOnly: true
+ });
+ }
+ deleteSiteSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let sites = this.sites;
+ sites.splice(this.activeIndex, 1);
+ this.setState({sites, siteOpen: false,isEdit: true,
+ isReadOnly: false,})
+ }
+ createSiteSuccess() {
+ let self = this;
+ this.alt.actions.global.hideScreenLoader.defer();
+ let sites = this.sites || [];
+ sites.push(self.siteData);
+ let newState = {
+ sites,
+ isEdit: true,
+ isReadOnly: true,
+ activeIndex: sites.length - 1
+ };
+ _.merge(newState)
+ this.setState(newState);
+ }
+ openRedundancyStateSocketSuccess(connection) {
+ let self = this;
+ let ws = window.multiplexer.channel(connection);
+ if (!connection) return;
+ this.setState({
+ socket: ws.ws,
+ channelId: connection
+ });
+ ws.onmessage = (socket) => {
+ try {
+ var data = JSON.parse(socket.data);
+ var newState = {status: data};
+ Utils.checkAuthentication(data.statusCode, function() {
+ self.closeSocket();
+ });
+
+ self.setState(newState);
+ } catch(error) {
+ console.log('Hit at exception in openRedundancyStateSocketSuccess', error)
+ }
+
+ }
+ ws.onclose = () => {
+ self.closeSocket();
+ }
+ }
+ closeSocket = () => {
+ if (this.socket) {
+ window.multiplexer.channel(this.channelId).close();
+ }
+ this.setState({
+ socket: null
+ })
+ }
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import RedundancyStore from './redundancyStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import 'style/layout.scss';
+import {Panel, PanelWrapper} from 'widgets/panel/panel';
+import {InputCollection, FormSection} from 'widgets/form_controls/formControls.jsx';
+
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import Input from 'widgets/form_controls/input.jsx';
+import Button, {ButtonGroup} from 'widgets/button/sq-button.jsx';
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import 'widgets/form_controls/formControls.scss';
+import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../node_modules/open-iconic/svg/trash.svg'
+import _ from 'lodash';
+import ROLES from 'utils/roleConstants.js';
+
+import './redundancy.scss';
+const PLATFORM = ROLES.PLATFORM;
+
+class SiteManagementDashboard extends React.Component {
+ constructor(props) {
+ super(props);
+ this.Store = this.props.flux.stores.hasOwnProperty('RedundancyStore') ? this.props.flux.stores.RedundancyStore : this.props.flux.createStore(RedundancyStore, 'RedundancyStore');
+ this.state = this.Store.getState();
+ this.actions = this.state.actions;
+ }
+ componentDidUpdate() {
+ let self = this;
+ ReactDOM.findDOMNode(this.siteList).addEventListener('transitionend', this.onTransitionEnd, false);
+ setTimeout(function() {
+ let element = self[`site-ref-${self.state.activeIndex}`]
+ element && !isElementInView(element) && element.scrollIntoView({block: 'end', behavior: 'smooth'});
+ })
+ }
+ componentWillMount() {
+ this.state = this.Store.getState();
+ this.Store.getRedundancy();
+ this.Store.listen(this.updateState);
+ }
+ componentWillUnmount() {
+ this.Store.unlisten(this.updateState);
+ }
+ updateState = (state) => {
+ this.setState(state);
+ }
+ updateInput = (key, e) => {
+ let property = key;
+ let siteData = this.state.siteData;
+ siteData[property] = e.target.value;
+ this.actions.handleUpdateInput({
+ siteData
+ })
+ }
+ updateServiceTargetInput = (serviceName, key, e) => {
+ let state = this.state;
+ let siteData = this.state.siteData;
+ let value = e.target.value;
+ let index = _.findIndex(state.siteData['target-endpoint'], function(o) {
+ return o.name == serviceName
+ });
+ if((index == undefined) || index == -1) {
+ index = siteData['target-endpoint'].push({name: serviceName}) - 1
+ }
+ siteData['target-endpoint'][index]['name'] = serviceName;
+ if(value.trim() == '') {
+ delete siteData['target-endpoint'][index][key];
+ } else {
+ siteData['target-endpoint'][index][key] = value
+ }
+ this.actions.handleUpdateInput({
+ siteData
+ })
+ }
+ updateInstanceInput = (index, key, e) => {
+ let siteData = this.state.siteData;
+ siteData['rw-instances'][index][key] = e.target.value;
+ this.actions.handleUpdateInput({
+ siteData
+ })
+ }
+ updateInstanceInputEndpoint = (index, serviceName, key, e) => {
+ let siteData = this.state.siteData;
+ let state = this.state;
+ let value = e.target.value;
+ let listIndex = _.findIndex(siteData['rw-instances'][index].endpoint, function(o) {
+ return o.name == serviceName
+ });
+ if(!siteData['rw-instances'][index].endpoint) {
+ siteData['rw-instances'][index].endpoint = []
+ }
+ if(listIndex == undefined || listIndex == -1) {
+ listIndex = siteData['rw-instances'][index].endpoint.push({name: serviceName}) - 1;
+ }
+ if(value.trim() == '') {
+ delete siteData['rw-instances'][index].endpoint[listIndex][key];
+ } else {
+ siteData['rw-instances'][index].endpoint[listIndex][key] = value
+ }
+ siteData['rw-instances'][index].endpoint[listIndex]['name'] = serviceName;
+ this.actions.handleUpdateInput({
+ siteData
+ })
+ }
+ addSite = () => {
+ this.actions.handleAddSite();
+ }
+ viewSite = (un, index) => {
+ this.actions.viewSite(un, index, true);
+ }
+ editSite = () => {
+ this.actions.editSite(false);
+ }
+ cancelEditSite = () => {
+ this.actions.editSite(true)
+ }
+ closePanel = () => {
+ this.actions.handleCloseSitePanel();
+ }
+
+ deleteSite = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (confirm('Are you sure you want to delete this site?')) {
+ this.Store.deleteSite({
+ 'site-name': this.state.siteData['site-name']
+ });
+ }
+ }
+ createSite = (e) => {
+ let self = this;
+ e.preventDefault();
+ e.stopPropagation();
+ let siteData = self.state.siteData;
+ if (this.validateFields(self, siteData)) {
+ this.Store.createSite(siteData);
+ }
+ }
+ updateSite = (e) => {
+ let self = this;
+ e.preventDefault();
+ e.stopPropagation();
+ let siteData = self.state.siteData;
+ if (this.validateFields(self, siteData)) {
+ this.Store.updateSite(siteData);
+ }
+ }
+ validateFields(self, siteData) {
+ if (!siteData['site-name'] || siteData['site-name'].trim() == '') {
+ self.props.flux.actions.global.showNotification("Please enter a site name");
+ return false;
+ }
+ let instanceInvalid = false;
+ siteData['rw-instances'] && siteData['rw-instances'].map(function(rw) {
+ if (!rw['rwinstance-id'] || rw['rwinstance-id'].trim() == '' ) {
+ instanceInvalid = true;;
+ }
+ });
+ if (instanceInvalid) {
+ self.props.flux.actions.global.showNotification("One or more of your RIFT.WARE Instances is missing it's FQDN/IP Address");
+ return false;
+ }
+ return true;
+ }
+ evaluateSubmit = (e) => {
+ if (e.keyCode == 13) {
+ if (this.props.isEdit) {
+ this.updateSite(e);
+ } else {
+ this.createSite(e);
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+ onTransitionEnd = (e) => {
+ this.actions.handleHideColumns(e);
+ console.log('transition end')
+ }
+ 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.editSite} />
+ </ButtonGroup>
+ );
+ if(!this.state.isReadOnly) {
+ formButtonsHTML = (
+ state.isEdit ?
+ (
+ <ButtonGroup className="buttonGroup">
+ <Button label="Update" type="submit" onClick={this.updateSite} />
+ <Button label="Delete" onClick={this.deleteSite} />
+ <Button label="Cancel" onClick={this.cancelEditSite} />
+ </ButtonGroup>
+ )
+ : (
+ <ButtonGroup className="buttonGroup">
+ <Button label="Create" type="submit" onClick={this.createSite} />
+ </ButtonGroup>
+ )
+ )
+ }
+
+ html = (
+ <PanelWrapper column>
+ <AppHeader nav={[{ name: 'SITES' }, { name: 'CONFIG', onClick: this.context.router.push.bind(this, { pathname: '/config' }) }, { name: 'STATUS', onClick: this.context.router.push.bind(this, { pathname: '/status' }) }]} />
+ <PanelWrapper className={`row siteManagement ${!this.state.siteOpen ? 'siteList-open' : ''}`} style={{'alignContent': 'center', 'flexDirection': 'row'}} >
+
+ <PanelWrapper ref={(div) => { this.siteList = div}} className={`column siteList expanded ${this.state.siteOpen ? 'collapsed ' : ' '} ${this.state.hideColumns ? 'hideColumns ' : ' '}`}>
+ <Panel title="Sites" style={{marginBottom: 0}} no-corners>
+ <div className="tableRow tableRow--header">
+ <div className="siteName">
+ Site Name
+ </div>
+ <div>
+ # of Instances
+ </div>
+ </div>
+ {state.sites && state.sites.map((u, k) => {
+ return (
+ <div onClick={self.viewSite.bind(null, u, k)} ref={(el) => this[`site-ref-${k}`] = el} className={`tableRow tableRow--data ${((self.state.activeIndex == k) && self.state.siteOpen) ? 'tableRow--data-active' : ''}`} key={k}>
+ <div
+ className={`siteName siteName-header ${((self.state.activeIndex == k) && self.state.siteOpen) ? 'activeSite' : ''}`}
+ >
+ {u['site-name']}
+ </div>
+ <div>
+ {u['rw-instances'] && u['rw-instances'].length}
+ </div>
+
+ </div>
+ )
+ })}
+ </Panel>
+ <SkyquakeRBAC className="rbacButtonGroup">
+ <ButtonGroup className="buttonGroup">
+ <Button label="Add Site" onClick={this.addSite} />
+ </ButtonGroup>
+ </SkyquakeRBAC>
+ </PanelWrapper>
+ <PanelWrapper onKeyUp={this.evaluateSubmit}
+ className={`SiteAdmin column`}>
+ <Panel
+ title={state.isEdit ? state.siteData['site-name'] : 'Create Site'}
+ style={{marginBottom: 0}}
+ hasCloseButton={this.closePanel}
+ no-corners>
+ <FormSection title="SITE INFO">
+ {
+ (state.isEditSite || state.isReadOnly) ?
+ <Input readonly={state.isReadOnly || this.state.isEdit} required label="Name" value={state.siteData['site-name']} onChange={this.updateInput.bind(null, 'site-name')} />
+ : null
+ }
+ <TextInput readonly={state.isReadOnly}
+ label='FQDN/IP Address'
+ pattern={state.siteIdPattern}
+ value={state.siteData['site-id']}
+ onChange={this.updateInput.bind(null, 'site-id')}
+ />
+ </FormSection>
+ {
+ <FormSection className="subSection" title="Service Endpoints">
+ <TextInput type="text" readonly={state.isReadOnly} onChange={self.updateServiceTargetInput.bind(self, 'ui-service', 'port')} label="UI Port" value={state.siteData['target-endpoint'] && state.siteData['target-endpoint'][ _.findIndex(state.siteData['target-endpoint'], function(o) { return o.name == 'ui-service' })] && state.siteData['target-endpoint'][ _.findIndex(state.siteData['target-endpoint'], function(o) { return o.name == 'ui-service' })].port} />
+ <TextInput type="text" readonly={state.isReadOnly} onChange={self.updateServiceTargetInput.bind(self, 'rest-service', 'port')} label="REST Port" value={state.siteData['target-endpoint'] && state.siteData['target-endpoint'][ _.findIndex(state.siteData['target-endpoint'], function(o) { return o.name == 'rest-service' })] && state.siteData['target-endpoint'][ _.findIndex(state.siteData['target-endpoint'], function(o) { return o.name == 'rest-service' })].port} />
+ </FormSection>
+ }
+ {
+ <FormSection title="RIFT.WARE INSTANCES">
+ {
+ state.siteData['rw-instances'] && state.siteData['rw-instances'].map(function(t, i) {
+ return <div key={i} className="rwInstance">
+ <h3>
+ <span className="title">INSTANCE</span>
+ {
+ (state.isReadOnly) ? null :
+ <span
+ onClick={self.actions.handleRemoveInstance.bind(null, {index: i})}
+ className="removeInput">
+ <img
+ src={imgRemove}
+ style={{marginBottom: '0px'}}/>
+ Remove
+ </span>
+ }
+ </h3>
+ <TextInput type="text"
+ label="FQDN/IP Address"
+ required
+ pattern={state.siteIdPattern}
+ readonly={!(t.isNew && (!state.isReadOnly))}
+ onChange={self.updateInstanceInput.bind(self, i, 'rwinstance-id')}
+ value={state.siteData['rw-instances'][i]['rwinstance-id']} />
+ <TextInput type="text"
+ label="Floating IP"
+ readonly={state.isReadOnly}
+ onChange={self.updateInstanceInput.bind(self, i, 'floating-ip')}
+ value={state.siteData['rw-instances'][i]['floating-ip']} />
+ <FormSection className="subSection" title="Service Endpoints">
+ <TextInput type="text"
+ label="UI Port"
+ readonly={state.isReadOnly}
+ onChange={self.updateInstanceInputEndpoint.bind(self, i, 'ui-service', 'port')}
+ value={state.siteData['rw-instances'] && state.siteData['rw-instances'][i] && state.siteData['rw-instances'][i].endpoint && state.siteData['rw-instances'][i].endpoint[ _.findIndex(state.siteData['rw-instances'][i].endpoint, function(o) { return o.name == 'ui-service' })
+ ] && state.siteData['rw-instances'][i].endpoint[ _.findIndex(state.siteData['rw-instances'][i].endpoint, function(o) { return o.name == 'ui-service' })
+ ].port}
+ />
+ <TextInput type="text"
+ label="REST Port"
+ readonly={state.isReadOnly}
+ onChange={self.updateInstanceInputEndpoint.bind(self, i,'rest-service', 'port')}
+ value={state.siteData['rw-instances'] && state.siteData['rw-instances'][i] && state.siteData['rw-instances'][i].endpoint && state.siteData['rw-instances'][i].endpoint[
+ _.findIndex(state.siteData['rw-instances'][i].endpoint, function(o) { return o.name == 'rest-service' })
+ ] && state.siteData['rw-instances'][i].endpoint[ _.findIndex(state.siteData['rw-instances'][i].endpoint, function(o) { return o.name == 'rest-service' })
+ ].port}
+ />
+ </FormSection>
+ </div>
+ })
+ }
+ {
+ (state.isReadOnly) ? null :
+ <span onClick={self.actions.handleAddInstance} className="addInput" ><img src={imgAdd} />Add Instance</span>
+ }
+ </FormSection>
+ }
+
+
+ </Panel>
+ <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} site={this.state.name} className="rbacButtonGroup">
+ {formButtonsHTML}
+ </SkyquakeRBAC>
+ </PanelWrapper>
+ </PanelWrapper>
+ </PanelWrapper>
+ );
+ return html;
+ }
+}
+// onClick={this.Store.update.bind(null, Account)}
+SiteManagementDashboard.contextTypes = {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+};
+
+SiteManagementDashboard.defaultProps = {
+ siteList: [],
+ selectedSite: {}
+}
+
+export default SkyquakeComponent(SiteManagementDashboard);
+
+
+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/>)
+ }
+}
+
+function showInput(e){
+ let target = e.target;
+ if(target.parentElement.classList.contains("addInput")) {
+ target = target.parentElement;
+ }
+ target.style.display = 'none';
+ target.parentElement.nextElementSibling.style.display = 'flex';
+ // e.target.parentElement.nextElementSibling.children[1].style.display = 'initial';
+}
+function hideInput(e){
+ let target = e.target;
+ if(target.parentElement.classList.contains("removeInput")) {
+ target = target.parentElement;
+ }
+ target.parentElement.style.display = 'none';
+ target.parentElement.previousElementSibling.children[1].style.display = 'inline';
+ target.previousSibling.value = '';
+}
+
+
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import RedundancyStore from './redundancyStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+import 'style/layout.scss';
+import {Panel, PanelWrapper} from 'widgets/panel/panel';
+import {InputCollection, FormSection} from 'widgets/form_controls/formControls.jsx';
+
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import Input from 'widgets/form_controls/input.jsx';
+import Button, {ButtonGroup} from 'widgets/button/sq-button.jsx';
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import 'widgets/form_controls/formControls.scss';
+import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../node_modules/open-iconic/svg/trash.svg'
+import _ from 'lodash';
+import ROLES from 'utils/roleConstants.js';
+
+import './redundancy.scss';
+const PROJECT_ROLES = ROLES.PROJECT;
+const PLATFORM = ROLES.PLATFORM;
+
+class StatusDashboard extends React.Component {
+ constructor(props) {
+ super(props);
+ this.Store = this.props.flux.stores.hasOwnProperty('RedundancyStore') ? this.props.flux.stores.RedundancyStore : this.props.flux.createStore(RedundancyStore, 'RedundancyStore');
+ this.state = this.Store.getState();
+ this.actions = this.state.actions;
+ }
+ componentDidUpdate() {
+ }
+ componentWillMount() {
+ this.state = this.Store.getState();
+ this.Store.getRedundancy();
+ this.Store.openRedundancyStateSocket();
+ this.Store.listen(this.updateState);
+ }
+ componentWillUnmount() {
+ this.Store.unlisten(this.updateState);
+ this.Store.closeSocket();
+ }
+ updateState = (state) => {
+ this.setState(state);
+ }
+ render() {
+ let self = this;
+ let html;
+ let props = this.props;
+ let state = this.state;
+ let STATUS = state.status;
+ /*
+
+ {
+ "active-instance": {
+ "active-inst-id": "10.64.210.20",
+ "is-this-instance-active": "true",
+ "site-name": "site20"
+ },
+ "vm-identity": "ebbd6444-d6a7-4eda-ab0d-e23fdbcdeffe",
+ "health-status": [
+ {
+ "state": "RUNNING_AS_ACTIVE",
+ "rwinstance-id": "10.64.210.20",
+ "site-name": "site20"
+ },
+ {
+ "state": "NO_RESPONSE",
+ "rwinstance-id": "10.64.210.33",
+ "site-name": "site33"
+ }
+ ]
+ }
+
+
+ */
+
+ html = (
+ <PanelWrapper onKeyUp={this.evaluateSubmit}
+ className={`SiteAdmin column`} column>
+ <AppHeader nav={[{ name: 'SITES', onClick: this.context.router.push.bind(this, { pathname: '/sites' }) }, { name: 'CONFIG', onClick: this.context.router.push.bind(this, { pathname: '/config' })}, { name: 'STATUS' }]} />
+ <PanelWrapper onKeyUp={this.evaluateSubmit}
+ className={`SiteAdmin column`} style={{overflow: 'auto'}} column>
+ <Panel
+ title="Active Instance"
+ style={{flex: '0 0 300px'}}
+ no-corners>
+ <TextInput type="text" label="VM ID" readonly={true} value={STATUS['vm-identity'] } />
+ <TextInput type="text" label="active instance ID" readonly={true} value={STATUS['active-instance'] && STATUS['active-instance']['active-inst-id'] } />
+ <TextInput type="text" label="Site Name" readonly={true} value={STATUS['active-instance'] && STATUS['active-instance']['site-name'] } />
+ </Panel>
+ <Panel
+ title="CONFIGURATION STATE"
+ style={{flex: '0 0 250px'}}
+ no-corners>
+ <div className="tableRow tableRow--header">
+ <div>
+ SITE NAME
+ </div>
+ <div>
+ INSTANCE IP
+ </div>
+ <div>
+ CURRENT STATE
+ </div>
+ <div>
+ PREVIOUS STATE
+ </div>
+ <div>
+ CONFIG GENERATION #
+ </div>
+ <div>
+ LAST PACKAGE UPDATE
+ </div>
+ </div>
+ {
+ STATUS['config-state'] && STATUS['config-state'].map((u, k) => {
+ return (
+ <div className={`tableRow tableRow--data`} key={k}>
+ <div>
+ {u['site-name'] || '--'}
+ </div>
+ <div>
+ {u['rwinstance-ip'] || '--'}
+ </div>
+ <div>
+ {u['current-state'] || '--'}
+ </div>
+ <div>
+ {u['previous-state'] || '--'}
+ </div>
+ <div>
+ {u['config-generation-number'] || '--'}
+ </div>
+ <div>
+ {u['last-package-update'] || '--'}
+ </div>
+
+
+ </div>
+ )
+ })
+ }
+
+
+ </Panel>
+ <Panel
+ title="HEALTH STATUS"
+ style={{flex: '0 0 250px'}}
+ no-corners>
+ <div className="tableRow tableRow--header">
+ <div>
+ RW INSTANCE
+ </div>
+ <div>
+ STATUS
+ </div>
+ </div>
+ {STATUS['health-status'] && STATUS['health-status'].map((u, k) => {
+ return (
+ <div className={`tableRow tableRow--data`} key={k}>
+ <div>
+ {u['rwinstance-id'] || '--'}
+ </div>
+ <div>
+ {u['state'] || '--'}
+ </div>
+
+
+ </div>
+ )
+ })}
+
+
+ </Panel>
+ </PanelWrapper>
+ </PanelWrapper>
+ );
+ return html;
+ }
+}
+// onClick={this.Store.update.bind(null, Account)}
+StatusDashboard.contextTypes = {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+};
+
+StatusDashboard.defaultProps = {
+ siteList: [],
+ selectedSite: {}
+}
+
+export default SkyquakeComponent(StatusDashboard);
--- /dev/null
+import Input from 'widgets/form_controls/input.jsx';
+
+var componentMap = {
+ leaf: {
+ component: 'input'
+ }
+}
+
+function buildFormElements (schema, onChange) {
+ var elements = [];
+ schema.map(function(s, i) {
+
+ })
+}
--- /dev/null
+/*
+ * 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);
+
+
--- /dev/null
+/*
+ * 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 CompressionPlugin = require("compression-webpack-plugin");
+
+// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
+process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
+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: '../' + htmlFilename,
+ template: frameworkPath + '/plugin-index.html'
+ }),
+ ]
+};
+
+if (process.argv.indexOf('--optimize-minimize') !== -1) {
+ // we are going to output a gzip file in the production process
+ config.output.filename = "gzip-" + config.output.filename;
+ config.plugins.push(new webpack.DefinePlugin({ // <-- key to reducing React's size
+ 'process.env': {
+ 'NODE_ENV': JSON.stringify('production')
+ }
+ }));
+ config.plugins.push(new CompressionPlugin({
+ asset: "[path]", // overwrite js file with gz file
+ algorithm: "gzip",
+ test: /\.(js)$/
+ }));
+}
+
+module.exports = config;
--- /dev/null
+# RIFT_IO_STANDARD_CMAKE_COPYRIGHT_HEADER(BEGIN)
+# Author(s): Kiran Kashalkar
+# Creation Date: 08/18/2015
+# RIFT_IO_STANDARD_CMAKE_COPYRIGHT_HEADER(END)
+
+##
+# DEPENDENCY ALERT
+# The submodule dependencies must be specified in the
+# .gitmodules.dep file at the top level (supermodule) directory
+# If this submodule depends other submodules remember to update
+# the .gitmodules.dep
+##
+
+cmake_minimum_required(VERSION 2.8)
+
+##
+# Submodule specific includes will go here,
+# These are specified here, since these variables are accessed
+# from multiple sub directories. If the variable is subdirectory
+# specific it must be declared in the subdirectory.
+##
+
+rift_externalproject_add(
+ user_management
+ DEPENDS skyquake
+ SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_COMMAND echo
+ BUILD_COMMAND
+ ${CMAKE_CURRENT_BINARY_DIR}/user_management/user_management-build/scripts/build.sh
+ INSTALL_COMMAND
+ ${CMAKE_CURRENT_SOURCE_DIR}/scripts/install.sh
+ ${CMAKE_CURRENT_BINARY_DIR}/user_management/user_management-build
+ ${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+ ${RIFT_SUBMODULE_INSTALL_PREFIX}/skyquake/${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+
+ BCACHE_COMMAND echo
+)
+
--- /dev/null
+{
+ "root": "public",
+ "name": "User Management",
+ "dashboard": "./dashboard/dashboard.jsx",
+ "order": 2,
+ "priority":1,
+ "admin_link": true,
+ "allow": [
+ "rw-rbac-platform:super-admin",
+ "rw-rbac-platform:platform-admin",
+ "rw-rbac-platform:platform-oper"],
+ "routes": [
+ {
+ "label": "User Management Dashboard",
+ "route": "user-management",
+ "component": "./dashboard/dashboard.jsx",
+ "type": "internal",
+ "allow": ["rw-rbac-platform:super-admin", "rw-rbac-platform:platform-admin", "rw-rbac-platform:platform-oper"]
+ },{
+ "label": "Platform Role Management",
+ "route": "platform",
+ "component": "./platformRoleManagement/platformRoleManagement.jsx",
+ "type": "external"
+ },
+ {
+ "label": "User Profile",
+ "route": "user-profile",
+ "component": "./userProfile/userProfile.jsx",
+ "type": "internal",
+ "unique" : true
+ }
+ ]
+}
--- /dev/null
+{
+ "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",
+ "compression-webpack-plugin": "^0.3.2",
+ "cors": "^2.7.1",
+ "css-loader": "^0.23.1",
+ "file-loader": "^0.8.5",
+ "html-webpack-plugin": "^2.9.0",
+ "http-proxy": "^1.12.0",
+ "loaders.css": "^0.1.2",
+ "node-sass": "^3.4.2",
+ "react-addons-css-transition-group": "^0.14.7",
+ "sass-loader": "^3.1.2",
+ "style-loader": "^0.13.0",
+ "webpack": "^1.3.0",
+ "webpack-dev-server": "^1.10.1"
+ }
+}
--- /dev/null
+/*
+ * 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;
--- /dev/null
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+PLUGIN_NAME=user_management
+# change to the directory of this script
+PLUGIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+source ../../../../skyquake/skyquake-build/scripts/build-plugin.sh
--- /dev/null
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+plugin=user_management
+source_dir=$1
+dest_dir=$2
+bcache_dir=$3
+
+# Create destination and build cache directories
+mkdir -p $dest_dir
+mkdir -p $bcache_dir
+
+# Create necessary directories under dest and cache dirs
+mkdir -p $dest_dir/framework
+mkdir -p $dest_dir/plugins
+mkdir -p $bcache_dir/framework
+mkdir -p $bcache_dir/plugins
+
+# Copy over built plugin's public folder, config.json, routes.js and api folder as per installed_plugins.txt
+mkdir -p $dest_dir/plugins/$plugin
+cp -Lrf $source_dir/public $dest_dir/plugins/$plugin/.
+cp -Lrf $source_dir/config.json $dest_dir/plugins/$plugin/.
+cp -Lrf $source_dir/routes.js $dest_dir/plugins/$plugin/.
+cp -Lrp $source_dir/api $dest_dir/plugins/$plugin/.
+tar -cf $dest_dir/plugins/$plugin/node_modules.tar node_modules package.json -C $source_dir
+#cp -Lrp $source_dir/node_modules $dest_dir/plugins/$plugin/.
+mkdir -p $bcache_dir/plugins/$plugin
+cp -Lrf $source_dir/public $bcache_dir/plugins/$plugin/.
+cp -Lrf $source_dir/config.json $bcache_dir/plugins/$plugin/.
+cp -Lrf $source_dir/routes.js $bcache_dir/plugins/$plugin/.
+cp -Lrp $source_dir/api $bcache_dir/plugins/$plugin/.
+tar -cf $bcache_dir/plugins/$plugin/node_modules.tar $source_dir/node_modules $source_dir/package.json
+#cp -Lrp $source_dir/node_modules $bcache_dir/plugins/$plugin/.
--- /dev/null
+/*
+ * 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('/*')
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import UserManagementStore from './userMgmtStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import 'style/layout.scss';
+import './userMgmt.scss';
+import { Panel, PanelWrapper } from 'widgets/panel/panel';
+import SkyquakeRBAC from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
+
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import Input from 'widgets/form_controls/input.jsx';
+import Button, { ButtonGroup } from 'widgets/button/sq-button.jsx';
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import 'widgets/form_controls/formControls.scss';
+import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../node_modules/open-iconic/svg/trash.svg';
+import { merge } from 'lodash';
+
+import ROLES from 'utils/roleConstants.js';
+const PLATFORM = ROLES.PLATFORM;
+
+class UserManagementDashboard extends React.Component {
+ constructor(props) {
+ super(props);
+ this.Store = this.props.flux.stores.hasOwnProperty('UserManagementStore') ? this.props.flux.stores.UserManagementStore : this.props.flux.createStore(UserManagementStore, 'UserManagementStore');
+ this.state = this.Store.getState();
+ this.actions = this.state.actions;
+ }
+ componentDidUpdate() {
+ let self = this;
+ ReactDOM.findDOMNode(this.UserList).addEventListener('transitionend', this.onTransitionEnd, false);
+ setTimeout(function () {
+ let element = self[`user-ref-${self.state.activeIndex}`]
+ element && !isElementInView(element) && element.scrollIntoView({ block: 'end', behavior: 'smooth' });
+ })
+ }
+ componentWillMount() {
+ this.Store.listen(this.updateState);
+ this.Store.getUsers();
+ }
+ componentWillUnmount() {
+ this.Store.unlisten(this.updateState);
+ }
+ updateState = (state) => {
+ this.setState(state);
+ }
+ updateInput = (key, e) => {
+ let property = key;
+ this.actions.handleUpdateInput({
+ [property]: e.target.value
+ })
+ }
+ platformChange = (platformRole, e) => {
+ this.actions.handlePlatformRoleUpdate(platformRole, e.currentTarget.checked);
+ }
+ addProjectRole = (e) => {
+ this.actions.handleAddProjectItem();
+ }
+ removeProjectRole = (i, e) => {
+ this.actions.handleRemoveProjectItem(i);
+ }
+ updateProjectRole = (i, e) => {
+ this.actions.handleUpdateProjectRole(i, e)
+ }
+ addUser = () => {
+ this.actions.handleAddUser();
+ }
+ viewUser = (un, index) => {
+ this.actions.viewUser(un, index);
+ }
+ editUser = () => {
+ this.actions.editUser(false);
+ }
+ cancelEditUser = () => {
+ this.actions.editUser(true)
+ }
+ closePanel = () => {
+ this.actions.handleCloseUserPanel();
+ }
+ // updateUser = (e) => {
+ // e.preventDefault();
+ // e.stopPropagation();
+
+ // this.Store.updateUser();
+ // }
+ deleteUser = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (confirm('Are you sure you want to delete this user?')) {
+ this.Store.deleteUser({
+ 'user-name': this.state['user-name'],
+ 'user-domain': this.state['user-domain']
+ });
+ }
+
+ }
+ createUser = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (!this.state['user-name'] || (this.state['user-name'].trim() == "")) {
+ this.props.actions.showNotification('Please enter a valid username');
+ return;
+ }
+
+ if((this.state['user-domain'].toLowerCase() == 'system') && !this.state['new-password'] || (this.state['new-password'].trim == "")) {
+ this.props.actions.showNotification('You must enter a password');
+ return;
+ }
+ if((this.state['user-domain'].toLowerCase() == 'system') && this.state['new-password'] != this.state['confirm-password']) {
+ this.props.actions.showNotification('Passwords do not match');
+ return;
+ } else {
+ let isDisabled = {};
+ if (this.state.disabled == "TRUE") {
+ isDisabled = {
+ disabled: [null]
+ }
+ }
+ this.Store.createUser(_.merge({
+ 'user-name': this.state['user-name'],
+ 'user-domain': this.state['user-domain'],
+ 'password': this.state['new-password'],
+ // 'confirm-password': this.state['confirm-password']
+ }, isDisabled));
+ }
+ }
+ updateUser = (e) => {
+ let self = this;
+ e.preventDefault();
+ e.stopPropagation();
+ let validatedPasswords = validatePasswordFields(this.state);
+ if (validatedPasswords) {
+ let isDisabled = {};
+ let password = {};
+ if (self.state.disabled == "TRUE") {
+ isDisabled = {
+ disabled: [null]
+ }
+ }
+ if (this.state['new-password'] != '') {
+ password = { 'password': this.state['new-password'] }
+ } else {
+ password = {
+ 'password': this.state.currentPassword
+ }
+ }
+ this.Store.updateUser(_.merge({
+ 'user-name': this.state['user-name'],
+ 'user-domain': this.state['user-domain'],
+ 'ui-state': this.context.userProfile.data['ui-state']
+ }, _.merge(isDisabled, password)));
+ }
+ function validatePasswordFields(state) {
+ let newOne = state['new-password'];
+ let confirmOne = state['confirm-password'];
+ if (!newOne && !confirmOne) {
+ // self.props.actions.showNotification('Please fill in all fields.');
+ return true;
+ }
+ if (newOne != confirmOne) {
+ self.props.actions.showNotification('Passwords do not match');
+ return false;
+ }
+ return {
+ 'new-password': newOne,
+ 'confirm-password': confirmOne
+ }
+ }
+ }
+ evaluateSubmit = (e) => {
+ if (e.keyCode == 13) {
+ if (this.state.isEdit) {
+ this.updateUser(e);
+ } else {
+ this.createUser(e);
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+ onTransitionEnd = (e) => {
+ this.actions.handleHideColumns(e);
+ console.log('transition end')
+ }
+ disableChange = (e) => {
+ let value = e.target.value;
+ value = value.toUpperCase();
+ this.actions.handleDisabledChange(value);
+ }
+ render() {
+ let self = this;
+ let html;
+ let props = this.props;
+ let state = this.state;
+ let passwordSectionHTML = null;
+ let formButtonsHTML = (
+ <ButtonGroup className="buttonGroup">
+ <Button label="EDIT" type="submit" onClick={this.editUser} />
+ </ButtonGroup>
+ );
+ if(!this.state.isReadOnly) {
+ passwordSectionHTML = (state['user-domain'].toLowerCase() == 'system') ?
+ ( this.state.isEdit ?
+ (
+ <FormSection title="PASSWORD CHANGE">
+ <Input label="NEW PASSWORD" type="password" value={state['new-password']} onChange={this.updateInput.bind(null, 'new-password')}/>
+ <Input label="REPEAT NEW PASSWORD" type="password" value={state['confirm-password']} onChange={this.updateInput.bind(null, 'confirm-password')}/>
+ </FormSection>
+ ) :
+ (
+ <FormSection title="CREATE PASSWORD">
+ <Input label="CREATE PASSWORD" type="password" value={state.newPassword} onChange={this.updateInput.bind(null, 'new-password')}/>
+ <Input label="REPEAT PASSWORD" type="password" value={state.repeatNewPassword} onChange={this.updateInput.bind(null, 'confirm-password')}/>
+ </FormSection>
+ )
+ )
+ : null;
+ formButtonsHTML = (
+ state.isEdit ?
+ (
+ <ButtonGroup className="buttonGroup">
+ <Button label="Delete" onClick={this.deleteUser} />
+ <Button label="Cancel" onClick={this.cancelEditUser} />
+ <Button label="Update" type="submit" onClick={this.updateUser} />
+ </ButtonGroup>
+ )
+ : (
+ <ButtonGroup className="buttonGroup">
+ <Button label="Cancel" onClick={this.closePanel} />
+ <Button label="Create" type="submit" onClick={this.createUser} />
+ </ButtonGroup>
+ )
+ )
+ }
+ html = (
+ <PanelWrapper column>
+ <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} >
+ <AppHeader nav={[{ name: 'USER MANAGEMENT' }, { name: 'PLATFORM ROLE MANAGEMENT', onClick: this.context.router.push.bind(this, { pathname: '/platform' }) }]} />
+ </SkyquakeRBAC>
+ <PanelWrapper className={`row userManagement ${!this.state.userOpen ? 'userList-open' : ''}`} style={{ 'flexDirection': 'row' }} >
+ <PanelWrapper ref={(div) => { this.UserList = div }} className={`column userList expanded ${this.state.userOpen ? 'collapsed ' : ' '} ${this.state.hideColumns ? 'hideColumns ' : ' '}`}>
+ <Panel title="User List" style={{ marginBottom: 0 }} no-corners>
+ <div className="tableRow tableRow--header">
+ <div className="userName">
+ Username
+ </div>
+ <div>
+ Domain
+ </div>
+ <div>
+ Status
+ </div>
+ </div>
+ {state.users && state.users.map((u, k) => {
+ let platformRoles = [];
+ for (let role in u.platformRoles) {
+ platformRoles.push(<div>{`${role}: ${u.platformRoles[role]}`}</div>)
+ }
+ return (
+ <div ref={(el) => this[`user-ref-${k}`] = el} className={`tableRow tableRow--data ${((self.state.activeIndex == k) && self.state.userOpen) ? 'tableRow--data-active' : ''}`}
+ key={k}
+ onClick={self.viewUser.bind(null, u, k)}>
+ <div
+ className={`userName userName-header ${((self.state.activeIndex == k) && self.state.userOpen) ? 'activeUser' : ''}`}
+ >
+ {u['user-name']}
+ </div>
+ <div>
+ {u['user-domain']}
+ </div>
+ <div>
+ {u['disabled'] ? "DISABLED" : "ENABLED"}
+ </div>
+ </div>
+ )
+ })}
+ </Panel>
+ <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} className="rbacButtonGroup">
+ <ButtonGroup className="buttonGroup">
+ <Button label="Add User" onClick={this.addUser} />
+ </ButtonGroup>
+ </SkyquakeRBAC>
+ </PanelWrapper>
+ <PanelWrapper onKeyUp={this.evaluateSubmit}
+ className={`userAdmin column`}>
+ <Panel
+ title={state.isEdit ? state['user-name'] : 'Create User'}
+ style={{ marginBottom: 0 }}
+ hasCloseButton={this.closePanel}
+ no-corners>
+ <FormSection title="USER INFO" className="userInfo">
+ {
+ (!state.isEditUser || state.isReadOnly) ?
+ <Input className="userInfo-section" readonly={state.isReadOnly || this.state.isEdit} label="Username" value={state['user-name']} onChange={this.updateInput.bind(null, 'user-name')} />
+ : null
+ }
+ <Input className="userInfo-section" readonly={state.isReadOnly || this.state.isEdit} label="Domain" value={state['user-domain']} onChange={this.updateInput.bind(null, 'user-domain')}></Input>
+ <Input className="userInfo-section"
+ type="radiogroup"
+ onChange={this.disableChange}
+ label="STATUS"
+ value={this.state.disabled}
+ options={[{ label: "DISABLED", value: "TRUE" }, { label: "ENABLED", value: "FALSE" }]}
+ readonly={state.isReadOnly}
+ readonlydisplay={this.state.disabled == "TRUE" ? "DISABLED" : "ENABLED"}
+ />
+ </FormSection>
+ <FormSection title="PLATFORM ROLES" style={{ display: 'none' }}>
+ <Input label="Super Admin" onChange={this.platformChange.bind(null, 'super_admin')} checked={state.platformRoles.super_admin} type="checkbox" />
+ <Input label="Platform Admin" onChange={this.platformChange.bind(null, 'platform_admin')} checked={state.platformRoles.platform_admin} type="checkbox" />
+ <Input label="Platform Oper" onChange={this.platformChange.bind(null, 'platform_oper')} checked={state.platformRoles.platform_oper} type="checkbox" />
+ </FormSection>
+ {
+ state.isEdit ?
+ <FormSection title="PROJECT ROLES">
+ <table className="userProfile-table">
+ <thead>
+ <tr>
+ <td>Project</td>
+ <td>Role</td>
+ </tr>
+ </thead>
+ <tbody>
+ {
+ this.state.projects && this.state.projects.ids && this.state.projects.ids.map((p, i) => {
+ let project = self.state.projects.data[p];
+ let userRoles = [];
+ return (
+ <tr key={i}>
+ <td>
+ {p}
+ </td>
+ <td>
+ {
+ project.map(function (k) {
+ return <div>{k}</div>
+ })
+ }
+ </td>
+ </tr>
+ )
+ })
+ }
+ </tbody>
+ </table>
+ </FormSection>
+ : null
+ }
+
+ {passwordSectionHTML}
+
+ </Panel>
+ <SkyquakeRBAC allow={[PLATFORM.SUPER, PLATFORM.ADMIN]} className="rbacButtonGroup">
+ {formButtonsHTML}
+ </SkyquakeRBAC>
+ </PanelWrapper>
+ </PanelWrapper>
+ </PanelWrapper>
+ );
+ return html;
+ }
+}
+// onClick={this.Store.update.bind(null, Account)}
+UserManagementDashboard.contextTypes = {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+};
+
+UserManagementDashboard.defaultProps = {
+ userList: [],
+ selectedUser: {}
+}
+
+export default SkyquakeComponent(UserManagementDashboard);
+
+
+function isElementInView(el) {
+ var rect = el && el.getBoundingClientRect() || {};
+
+ return (
+ rect.top >= 0 &&
+ rect.left >= 0 &&
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
+ );
+}
+
+
+// isReadOnly={state.isReadOnly} disabled={state.disabled} onChange={this.disableChange}
+
+class isDisabled extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+ render() {
+ let props = this.props;
+ return (<div />)
+ }
+}
+
+/**
+ * AddItemFn:
+ */
+class InputCollection extends React.Component {
+ constructor(props) {
+ super(props);
+ this.collection = props.collection;
+ }
+ buildTextInput(onChange, v, i) {
+ return (
+ <Input
+ readonly={this.props.readonly}
+ style={{ flex: '1 1' }}
+ key={i}
+ value={v}
+ onChange={onChange.bind(null, i)}
+ />
+ )
+ }
+ buildSelectOption(initial, options, onChange, v, i) {
+ return (
+ <SelectOption
+ readonly={this.props.readonly}
+ key={`${i}-${v.replace(' ', '_')}`}
+ intial={initial}
+ defaultValue={v}
+ options={options}
+ onChange={onChange.bind(null, i)}
+ />
+ );
+ }
+ showInput() {
+
+ }
+ render() {
+ const props = this.props;
+ let inputType;
+ let className = "InputCollection";
+ if (props.className) {
+ className = `${className} ${props.className}`;
+ }
+ if (props.type == 'select') {
+ inputType = this.buildSelectOption.bind(this, props.initial, props.options, props.onChange);
+ } else {
+ inputType = this.buildTextInput.bind(this, props.onChange)
+ }
+ let html = (
+ <div className="InputCollection-wrapper">
+ {props.collection.map((v, i) => {
+ return (
+ <div key={i} className={className} >
+ {inputType(v, i)}
+ {
+ props.readonly ? null : <span onClick={props.RemoveItemFn.bind(null, i)} className="removeInput"><img src={imgRemove} />Remove</span>}
+ </div>
+ )
+ })}
+ {props.readonly ? null : <span onClick={props.AddItemFn} className="addInput"><img src={imgAdd} />Add</span>}
+ </div>
+ );
+ return html;
+ }
+}
+
+InputCollection.defaultProps = {
+ input: Input,
+ collection: [],
+ onChange: function (i, e) {
+ console.log(`
+ Updating with: ${e.target.value}
+ At index of: ${i}
+ `)
+ },
+ AddItemFn: function (e) {
+ console.log(`Adding a new item to collection`)
+ },
+ RemoveItemFn: function (i, e) {
+ console.log(`Removing item from collection at index of: ${i}`)
+ }
+}
+
+class FormSection extends React.Component {
+ render() {
+ let className = 'FormSection ' + this.props.className;
+ let html = (
+ <div
+ style={this.props.style}
+ className={className}
+ >
+ <div className="FormSection-title">
+ {this.props.title}
+ </div>
+ <div className="FormSection-body">
+ {this.props.children}
+ </div>
+ </div>
+ );
+ return html;
+ }
+}
+
+FormSection.defaultProps = {
+ className: ''
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+/* If there's time this really needs to be rewritten. Ideally with smooth animations.*/
+@import "style/_colors.scss";
+
+.userManagement {
+ max-width: 900px;
+
+ .skyquakePanel-wrapper {
+ overflow-x: hidden;
+ }
+ .userList {
+
+ -ms-flex: 0 1 200px;
+ -webkit-box-flex: 0;
+ flex: 0 1 200px;
+
+ .activeUser {
+ font-weight:bold;
+ }
+
+ /* transition: all 2s;*/
+ &.expanded {
+ -ms-flex: 1 1 100%;
+ -webkit-box-flex: 1;
+ flex: 1 1 100%;
+ /* transition: all 300ms;*/
+ .tableRow>div:not(.userName) {
+ opacity: 1;
+ /* width:auto;*/
+ /* transition: width 600ms;*/
+ /* transition: opacity 300ms;*/
+ }
+ &.collapsed {
+ -ms-flex: 0 1 200px;
+ -webkit-box-flex: 0;
+ flex: 0 1 200px;
+ /* transition: all 2s;*/
+ .tableRow>div:not(.userName) {
+ /* opacity: 0;*/
+ /* width:0px;*/
+ display:none;
+ overflow:hidden;
+ /* transition: all 600ms;*/
+ }
+ }
+ }
+ &.hideColumns {
+ overflow:hidden;
+ >div {
+ overflow:hidden;
+ }
+ .tableRow>div:not(.userName) {
+ width: 0px;
+ /* transition: all 600ms;*/
+ }
+ .userName {
+ &--header {
+ /* display:none;*/
+ }
+ }
+ }
+ .userName {
+ &:not(:first-child) {
+ cursor:pointer;
+ }
+ }
+
+ }
+ .userInfo {
+ &-section {
+ padding: 0.5rem;
+ margin-bottom: 0;
+ &:nth-child(even) {
+ background:$neutral-dark-0;
+ }
+ }
+ }
+ .userAdmin {
+ -ms-flex: 1 1;
+ -webkit-box-flex: 1;
+ flex: 1 1;
+ width:auto;
+ opacity:1;
+ }
+ &.userList-open {
+ .userAdmin {
+ -ms-flex: 0 1 0px;
+ -webkit-box-flex: 0;
+ flex: 0 1 0px;
+ opacity:0;
+ /* width: 0px;*/
+ display:none;
+ /* transition: opacity 300ms;*/
+ /* transition: width 600ms;*/
+
+ }
+ }
+ .rbacButtonGroup, .buttonSection {
+ margin: 0 0.5rem 0.5rem;
+ background: #ddd;
+ padding-bottom: 0.5rem;
+ padding: 0.5rem 0;
+ }
+ .buttonGroup {
+ border-top: #d3d3d3 1px solid;
+ padding-top:0.5rem;
+ }
+ table {
+ font-size: 0.8rem;
+ thead {
+ border-bottom:1px solid #d3d3d3;
+ td{
+ font-weight:bold;
+ }
+ }
+ td{
+ padding:0.25rem 0.5rem;
+ vertical-align: middle;
+ .checkbox {
+ -ms-flex-pack:center;
+ -webkit-box-pack:center;
+ justify-content:center;
+ }
+ }
+ }
+ .sqCheckBox {
+ width:100%;
+ label input {
+ min-width:auto;
+ }
+ }
+ .FormSection label {
+ -webkit-box-flex: 0;
+ -ms-flex: 0 1;
+ flex: 0 1;
+ margin-right: 1rem;
+ }
+}
+
+
+
+
+.tableRow {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ padding: 0.25rem;
+ >div {
+ padding:0.25rem 1rem 0.25rem 0;
+ -ms-flex: 1 1 33%;
+ -webkit-box-flex: 1;
+ flex: 1 1 33%;
+ }
+ &--header {
+ font-weight:bold;
+ }
+ &--data {
+ &:nth-child(even) {
+ background:$neutral-dark-0;
+ }
+ &:hover:not(&-active) {
+ background:$neutral-dark-1;
+ }
+ &:hover, .activeUser, &-active{
+ cursor:pointer;
+ color:white;
+ }
+ .activeUser, &-active{
+ background: #00acee !important;
+ }
+ }
+
+
+ .userProfile {
+ &-table {
+ thead{
+ font-weight:bold;
+ }
+ font-size: 1rem;
+ tr {
+ td {
+ vertical-align:top;
+ }
+ }
+ }
+ }
+}
+
+
+
+
+.addInput, .removeInput {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ -ms-flex-align:center;
+ -webkit-box-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;
+ }
+}
+
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+module.exports = function(Alt) {
+ return Alt.generateActions(
+ 'handleUpdateInput',
+ 'handleAddProjectItem',
+ 'handleRemoveProjectItem',
+ 'handleUpdateProjectRole',
+ 'viewUser',
+ 'editUser',
+ 'handleCloseUserPanel',
+ 'handleHideColumns',
+ 'getUsersSuccess',
+ 'getUsersNotification',
+ 'handleDisabledChange',
+ 'handlePlatformRoleUpdate',
+ 'handleAddUser',
+ 'handleCreateUser',
+ 'handleUpdateUser',
+ 'updateUserSuccess',
+ 'createUserSuccess',
+ 'deleteUserSuccess',
+ 'handleDisabledChange'
+ );
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import $ from 'jquery';
+var Utils = require('utils/utils.js');
+let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
+let HOST = API_SERVER;
+let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
+let DEV_MODE = require('utils/rw.js').getSearchParams(window.location).dev_mode || false;
+
+if (DEV_MODE) {
+ HOST = window.location.protocol + '//' + window.location.hostname;
+}
+
+
+
+
+module.exports = function(Alt) {
+ return {
+ getUsers: {
+ remote: function() {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/user?api_server=${API_SERVER}`,
+ type: 'GET',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data.user);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error retrieving the resource orchestrator information.'
+ }),
+ success: Alt.actions.global.getUsersSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ updateUser: {
+ remote: function(state, user) {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/user?api_server=${API_SERVER}`,
+ type: 'PUT',
+ data: user,
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error updating the user.'
+ }),
+ success: Alt.actions.global.updateUserSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ deleteUser: {
+ remote: function(state, user) {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/user/${encodeURIComponent(user['user-name'])}/${encodeURIComponent(user['user-domain'])}?api_server=${API_SERVER}`,
+ type: 'DELETE',
+ data: user,
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error deleting the user.'
+ }),
+ success: Alt.actions.global.deleteUserSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ createUser: {
+ remote: function(state, user) {
+
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/user?api_server=${API_SERVER}`,
+ type: 'POST',
+ data: user,
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error updating the account.'
+ }),
+ success: Alt.actions.global.createUserSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ }
+ }
+}
+
+function interceptResponse (responses) {
+ return function(data, action, args) {
+ if(responses.hasOwnProperty(data)) {
+ return {
+ type: data,
+ msg: responses[data]
+ }
+ } else {
+ return data;
+ }
+ }
+}
+
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import UserManagementActions from './userMgmtActions.js';
+import UserManagementSource from './userMgmtSource.js';
+import _ from 'lodash';
+export default class UserManagementStore {
+ constructor() {
+ this.actions = UserManagementActions(this.alt);
+ this.bindActions(this.actions);
+ this.registerAsync(UserManagementSource);
+ this.users = [];
+ this['user-name'] = '';
+ this['user-domain'] = 'system';
+ this.disabled = "FALSE";
+ this.platformRoles = {
+ super_admin: false,
+ platform_admin: false,
+ platform_oper: false
+ };
+ this.projectRoles = ['Project:Role'];
+ this.projectRolesOptions = ['Choose your adventure', 'Project:Role', 'Project:Another Role'];
+ this.currentPassword = '';
+ this['old-password'] = '';
+ this['new-password'] = '';
+ this['confirm-password'] = '';
+
+ this.activeIndex = null;
+ this.isReadOnly = true;
+ this.userOpen = false;
+ this.hideColumns = false;
+ //There is probably a better way of handling the view/edit/readonly matrix conditions for some of these inputs. Should definitely revist
+ this.isEdit = false;
+ this.isEditUser = false;
+ // this.exportPublicMethods({})
+ }
+ /**
+ * [handleFieldUpdate description]
+ * @param {Object} data {
+ * [store_property] : [value]
+ * }
+ * @return {[type]} [description]
+ */
+ handleUpdateInput(data) {
+ this.setState(data);
+ }
+ handleAddProjectItem(item) {
+ let projectRoles = this.projectRoles;
+ projectRoles.push('');
+ this.setState({projectRoles});
+ }
+ handleRemoveProjectItem(i) {
+ let projectRoles = this.projectRoles;
+ projectRoles.splice(i, 1);
+ console.log('Removing', projectRoles)
+ this.setState({projectRoles});
+ }
+ handleUpdateProjectRole(data) {
+ let i = data[0];
+ let e = data[1];
+ let projectRoles = this.projectRoles
+ projectRoles[i] = JSON.parse(e.currentTarget.value);
+ this.setState({
+ projectRoles
+ });
+ }
+ viewUser(data) {
+ let user = data[0];
+ let userIndex = data[1];
+
+ let ActiveUser = {
+ 'user-name': user['user-name'],
+ 'user-domain': user['user-domain'],
+ platformRoles: user.platformRoles || this.platformRoles,
+ disabled: user.hasOwnProperty('disabled').toString().toUpperCase(),
+ projectRoles: user.projectRoles || this.projectRoles,
+ projects: user.projects,
+ currentPassword: user.password
+ }
+ let state = _.merge({
+ activeIndex: userIndex,
+ userOpen: true,
+ isEdit: true,
+ isReadOnly: true
+ }, ActiveUser);
+ this.setState(state)
+ }
+ editUser(isEdit) {
+ this.setState({
+ isEditUser: !isEdit,
+ isReadOnly: isEdit
+ })
+ }
+ handleCloseUserPanel() {
+ this.setState({
+ userOpen: false,
+ isEdit: false,
+ isReadOnly: true
+ })
+ }
+ handleHideColumns(e) {
+ if(this.userOpen && e.currentTarget.classList.contains('hideColumns')) {
+ this.setState({
+ hideColumns: true
+ })
+ } else {
+ this.setState({
+ hideColumns: false
+ })
+ }
+ }
+ handleDisabledChange(isDisabled){
+ this.setState({
+ disabled: isDisabled
+ })
+ }
+ handlePlatformRoleUpdate(data){
+ let platform_role = data[0];
+ let checked = data[1];
+ let platformRoles = this.platformRoles;
+ platformRoles[platform_role] = checked;
+ this.setState({
+ platformRoles
+ })
+ }
+ resetUser() {
+ let username = '';
+ let domain = 'system';
+ let disabled = "FALSE";
+ let platformRoles = {
+ super_admin: false,
+ platform_admin: false,
+ platform_oper: false
+ };
+ let projectRoles = [];
+ let currentPassword = '';
+ let oldPassword = '';
+ let newPassword = '';
+ let confirmPassword = '';
+ return {
+ 'user-name' : username,
+ 'user-domain' : domain,
+ disabled,
+ platformRoles,
+ projectRoles,
+ currentPassword,
+ 'old-password': oldPassword,
+ 'new-password': newPassword,
+ 'confirm-password': confirmPassword
+ }
+ }
+ resetPassword() {
+ let currentPassword = '';
+ let oldPassword = '';
+ let newPassword = '';
+ let confirmPassword = '';
+ return {
+ currentPassword,
+ 'old-password': oldPassword,
+ 'new-password': newPassword,
+ 'confirm-password': confirmPassword
+ }
+ }
+ handleAddUser() {
+ this.setState(_.merge( this.resetUser() ,
+ {
+ isEditUser: false,
+ isEdit: false,
+ userOpen: true,
+ activeIndex: null,
+ isReadOnly: false
+ }
+ ))
+ }
+
+ getUsersSuccess(users) {
+ this.alt.actions.global.hideScreenLoader.defer();
+ this.setState({users});
+ }
+ updateUserSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let users = this.users || [];
+ users[this.activeIndex] = {
+ 'user-name': this['user-name'],
+ 'user-domain': this['user-domain'],
+ platformRoles: this.platformRoles,
+ disabled: this.disabled,
+ projectRoles: this.projectRoles
+ }
+ this.setState({
+ users,
+ isEdit: true,
+ isReadOnly: true
+ })
+ }
+ deleteUserSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let users = this.users;
+ users.splice(this.activeIndex, 1);
+ this.setState({users, userOpen: false})
+ }
+ createUserSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let users = this.users || [];
+ users.push({
+ 'user-name': this['user-name'],
+ 'user-domain': this['user-domain'],
+ platformRoles: this.platformRoles,
+ disabled: this.disabled,
+ projectRoles: this.projectRoles,
+ });
+ let newState = {
+ users,
+ isEdit: true,
+ isReadOnly: true,
+ activeIndex: users.length - 1
+ };
+ _.merge(newState, this.resetPassword())
+ this.setState(newState);
+ }
+}
--- /dev/null
+/*
+ * 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);
+
+
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import PlatformRoleManagementStore from './platformRoleManagementStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import 'style/layout.scss';
+import './platformRoleManagement.scss';
+import {Panel, PanelWrapper} from 'widgets/panel/panel';
+import {InputCollection, FormSection} from 'widgets/form_controls/formControls.jsx';
+
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import Input from 'widgets/form_controls/input.jsx';
+import Button, {ButtonGroup} from 'widgets/button/sq-button.jsx';
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import 'widgets/form_controls/formControls.scss';
+import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../node_modules/open-iconic/svg/trash.svg'
+import _ from 'lodash';
+
+class PlatformRoleManagement extends React.Component {
+ constructor(props) {
+ super(props);
+ this.Store = this.props.flux.stores.hasOwnProperty('PlatformRoleManagementStore') ? this.props.flux.stores.PlatformRoleManagementStore : this.props.flux.createStore(PlatformRoleManagementStore,'PlatformRoleManagementStore');
+ this.state = this.Store.getState();
+ this.actions = this.state.actions;
+ this.Store.getPlatform();
+ this.Store.getUsers();
+ }
+ componentDidUpdate() {
+
+ }
+ componentWillMount() {
+ this.Store.listen(this.updateState);
+ }
+ componentWillUnmount() {
+ this.Store.unlisten(this.updateState);
+ }
+ updateState = (state) => {
+ this.setState(state);
+ }
+ updateInput = (key, e) => {
+ let property = key;
+ this.actions.handleUpdateInput({
+ [property]:e.target.value
+ })
+ }
+ disabledChange = (e) => {
+ this.actions.handleDisabledChange(e.target.checked);
+ }
+ platformChange = (platformRole, e) => {
+ this.actions.handlePlatformRoleUpdate(platformRole, e.currentTarget.checked);
+ }
+ editProject = () => {
+ this.actions.editPlatform(false);
+ }
+ cancelEditPlatform = () => {
+ this.actions.editPlatform(true)
+ }
+ closePanel = () => {
+ this.actions.handleCloseProjectPanel();
+ }
+ updatePlatform = (e) => {
+ let self = this;
+ e.preventDefault();
+ e.stopPropagation();
+ let platformUsers = self.state.platformUsers;
+ let cleanUsers = this.cleanUsers(platformUsers);
+ this.Store.updatePlatform({
+ 'user': JSON.stringify(cleanUsers)
+ }
+ );
+ }
+ cleanUsers(platformUsers) {
+ let self = this;
+ let cleanUsers = [];
+ //Remove null values from role
+ platformUsers.map((u) => {
+ let cleanRoles = [];
+ u.role && u.role.map((r,i) => {
+ let role = {};
+ //Platform user can not change role of itself.
+ if(r.role){
+ //removing key for rbac-platform
+ delete r.keys;
+ cleanRoles.push(r)
+ }
+ });
+ u.role = cleanRoles;
+ // if (u['user-name'] != self.context.userProfile.userId) {
+ cleanUsers.push(u);
+ // }
+ });
+ return cleanUsers;
+ }
+ evaluateSubmit = (e) => {
+ if (e.keyCode == 13) {
+ if (this.props.isEdit) {
+ this.updatePlatform(e);
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+ updateSelectedUser = (e) => {
+ this.setState({
+ selected
+ })
+ }
+ addUserToProject = (e) => {
+ this.actions.handleAddUser();
+ }
+ removeUserFromProject = (userIndex, e) => {
+ this.actions.handleRemoveUserFromProject(userIndex);
+ }
+ updateUserRoleInProject = (userIndex, roleIndex, e) => {
+ this.actions.handleUpdateUserRoleInProject({
+ userIndex,
+ roleIndex,
+ value: JSON.parse(e.target.value)
+ })
+ }
+ toggleUserRoleInProject = (userIndex, roleIndex, e) => {
+ this.actions.handleToggleUserRoleInProject({
+ userIndex,
+ roleIndex,
+ checked: JSON.parse(e.currentTarget.checked)
+ })
+ }
+ removeRoleFromUserInProject = (userIndex, roleIndex, e) => {
+ this.actions.handleRemoveRoleFromUserInProject({
+ userIndex,
+ roleIndex
+ })
+ }
+ addRoleToUserInProject = (userIndex, e) => {
+ this.actions.addRoleToUserInProject(userIndex);
+ }
+ onTransitionEnd = (e) => {
+ this.actions.handleHideColumns(e);
+ console.log('transition end')
+ }
+ disableChange = (e) => {
+ let value = e.target.value;
+ value = value.toUpperCase();
+ if (value=="TRUE") {
+ value = true;
+ } else {
+ value = false;
+ }
+ console.log(value)
+ }
+ render() {
+ let self = this;
+ let html;
+ let props = this.props;
+ let state = this.state;
+ let passwordSectionHTML = null;
+ let formButtonsHTML = (
+ <ButtonGroup className="buttonGroup">
+ <Button label="EDIT" type="submit" onClick={this.editProject} />
+ </ButtonGroup>
+ );
+ let platformUsers = self.state.platformUsers;
+ let availableDomains = state.domains;
+ let availableUsers = state.users && state.users.filter((u) => {
+ return state.selectedDomain == u['user-domain'] && _.findIndex(platformUsers, (s) => {return (s['user-name'] == u['user-name']) && (u['user-domain'] == s['user-domain'])}) == -1
+ }).map((u) => {
+ return {
+ label: `${u['user-name']}`,
+ value: u
+ }
+ });
+
+
+ if(!this.state.isReadOnly) {
+ formButtonsHTML = (
+ state.isEdit ?
+ (
+ <ButtonGroup className="buttonGroup">
+ <Button label="Update" type="submit" onClick={this.updatePlatform} />
+ <Button label="Cancel" onClick={this.cancelEditPlatform} />
+ </ButtonGroup>
+ )
+ : (
+ <ButtonGroup className="buttonGroup">
+ <Button label="Edit" type="submit" onClick={this.updatePlatform} />
+ </ButtonGroup>
+ )
+ )
+ }
+
+ html = (
+ <PanelWrapper column>
+ <AppHeader nav={[{name: 'USER MANAGEMENT', onClick: this.context.router.push.bind(this, {pathname: '/'})}, {name: 'PLATFORM ROLE MANAGEMENT'}]}/>
+ <PanelWrapper className={`row projectManagement ${false ? 'projectList-open' : ''}`} style={{'alignContent': 'center', 'flexDirection': 'row'}} >
+ <PanelWrapper onKeyUp={this.evaluateSubmit}
+ className={`ProjectAdmin column`}>
+ <Panel
+ title="Manage Roles"
+ style={{marginBottom: 0}}
+ no-corners>
+ <FormSection title="USER ROLES">
+
+ <table>
+ <thead>
+ <tr>
+ <td>Domain</td>
+ <td>User Name</td>
+ {
+ state.roles.map((r,i) => {
+ return <td key={i}>{r}</td>
+ })
+ }
+ </tr>
+ </thead>
+ <tbody>
+ {
+ state.platformUsers.map((u,i)=> {
+ let userRoles = u.role && u.role.map((r) => {
+ return r.role;
+ }) || [];
+ return (
+ <tr key={i}>
+ <td>
+ {u['user-domain']}
+ </td>
+ <td>
+ {u['user-name']}
+ </td>
+ {
+ state.roles.map((r,j) => {
+ return <td key={j}><Input readonly={state.isReadOnly} type="checkbox" onChange={self.toggleUserRoleInProject.bind(self, i, j)} checked={(userRoles.indexOf(r) > -1)} /></td>
+ })
+ }
+ {!state.isReadOnly ? <td><span
+ className="removeInput"
+ onClick={self.removeUserFromProject.bind(self, i)}
+ >
+ <img src={imgRemove} />
+
+ </span></td> : null}
+ </tr>
+ )
+ })
+ }
+ </tbody>
+ </table>
+ {
+ !state.isReadOnly ?
+ <div className="tableRow tableRow--header">
+ <div>
+ <div className="addUser">
+ {
+ availableDomains.length == 1 ?
+ <SelectOption
+ label="Domain"
+ onChange={this.actions.handleSelectedDomain}
+ defaultValue={state.selectedDomain || availableDomains[0]}
+ initial={false}
+ readonly={true}
+ options={availableDomains}
+ ref={(el) => self.selectUserList = el}
+ /> :
+ <SelectOption
+ label="Domain"
+ onChange={this.actions.handleSelectedDomain}
+ value={state.selectedDomain || availableDomains[0]}
+ initial={false}
+ options={availableDomains}
+ ref={(el) => self.selectUserList = el}
+ />
+ }
+ {
+ availableUsers.length ?
+ <SelectOption
+ label="Username"
+ onChange={this.actions.handleSelectedUser}
+ value={state.selectedUser}
+ initial={true}
+ options={availableUsers}
+ ref={(el) => self.selectUserList = el}
+ /> :
+ <label className="noUsersAvailable">
+ <span>Username</span>
+ <span style={{display: 'block',
+ marginTop: '0.8rem', color: '#666'}}>No Available Users for this Domain</span></label>
+ }
+ {
+ availableUsers.length ?
+ <span className="addInput" onClick={this.addUserToProject}><img src={imgAdd} />
+ Add User
+ </span> :
+ null
+ }
+
+ </div>
+ </div>
+ </div> : null
+ }
+
+ </FormSection>
+ </Panel>
+ {formButtonsHTML}
+ </PanelWrapper>
+ </PanelWrapper>
+ </PanelWrapper>
+ );
+ return html;
+ }
+}
+// onClick={this.Store.update.bind(null, Account)}
+PlatformRoleManagement.contextTypes = {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+};
+
+PlatformRoleManagement.defaultProps = {
+ projectList: [],
+ selectedProject: {}
+}
+
+export default SkyquakeComponent(PlatformRoleManagement);
+
+
+function isElementInView(el) {
+ var rect = el && el.getBoundingClientRect() || {};
+
+ return (
+ rect.top >= 0 &&
+ rect.left >= 0 &&
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
+ );
+}
+
+
+// isReadOnly={state.isReadOnly} disabled={state.disabled} onChange={this.disableChange}
+
+class isDisabled extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+ render() {
+ let props = this.props;
+ return (<div/>)
+ }
+}
+
+
+
+
--- /dev/null
+/*
+ * 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;
+ -webkit-box-flex: 0;
+ flex: 0 1 200px;
+
+ .activeUser {
+ font-weight:bold;
+ }
+
+ /* transition: all 2s;*/
+ &.expanded {
+ -ms-flex: 1 1 100%;
+ -webkit-box-flex: 1;
+ 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;
+ -webkit-box-flex: 0;
+ 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;
+ -webkit-box-flex: 1;
+ flex: 1 1;
+ width:auto;
+ opacity:1;
+
+ textarea{
+ height: 100px;
+ }
+ }
+ &.projectList-open {
+ .projectAdmin {
+ -ms-flex: 0 1 0px;
+ -webkit-box-flex: 0;
+ 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:-webkit-box;
+ display:flex;
+ -ms-flex-direction:row;
+ -webkit-box-orient:horizontal;
+ -webkit-box-direction:normal;
+ flex-direction:row;
+ label {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -ms-flex: 0 1;
+ -webkit-box-flex: 0;
+ flex: 0 1;
+
+ width:150px;
+ span {
+ margin-bottom: 0.5rem;
+ }
+ span:nth-child(2) {
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ }
+
+ margin-right: 1rem;
+ select {
+ width:150px;
+ }
+ }
+ .noUsersAvailable {
+ display:-webkit-box;
+ display:-ms-flexbox;
+ display:flex;
+ -webkit-box-align: start;
+ -ms-flex-align: start;
+ align-items: flex-start;
+ margin-bottom: 0.75rem;
+ -webkit-box-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ div {
+ width:260px;
+ margin-bottom: 0.5rem;
+ }
+ }
+ }
+ .projectUsers {
+ .userName {
+ -ms-flex-pack: start;
+ -webkit-box-pack: start;
+ justify-content: flex-start;
+ padding-top: 0.75rem;
+ }
+ select {
+ margin-bottom:0.5rem;
+ }
+ .addRole {
+ margin:.25rem 0;
+ }
+ .buttonGroup {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ }
+
+ }
+ .projectUsers.tableRow--data:hover {
+ background:none;
+ color: black;
+ }
+
+ table {
+ thead {
+ border-bottom:1px solid #d3d3d3;
+ td{
+ font-weight:bold;
+ }
+ }
+ td{
+ padding:0.25rem 0.5rem;
+ vertical-align: middle;
+ .checkbox {
+ -ms-flex-pack:center;
+ -webkit-box-pack:center;
+ justify-content:center;
+ }
+ }
+ tbody tr {
+ &:nth-child(even) {
+ background:$neutral-dark-0;
+ }
+ }
+ }
+}
+
+
+
+.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;
+ -webkit-box-flex: 1;
+ 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:-webkit-box;
+ display:flex;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -ms-flex-align: center;
+ -webkit-box-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:-webkit-box;
+ display:flex;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ padding: 0.25rem;
+ >div {
+ padding: 0.25rem;
+ -ms-flex: 1 1 33%;
+ -webkit-box-flex: 1;
+ flex: 1 1 33%;
+ display: -ms-flexbox;
+ display: -webkit-box;
+ display: flex;
+ -ms-flex-direction: column;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ flex-direction: column;
+ -ms-flex-pack: center;
+ -webkit-box-pack: center;
+ justify-content: center;
+ }
+ &--header {
+ font-weight:bold;
+ }
+ &--data {
+ &:nth-child(even) {
+ background:$neutral-dark-0;
+ }
+ &:hover:not(&-active) {
+ background:$neutral-dark-1;
+ }
+ &:hover, .activeUser, &-active{
+ cursor:pointer;
+ color:white;
+ }
+ .activeUser, &-active{
+ background: #00acee;
+ }
+ }
+}
+
+.addInput, .removeInput {
+ display:-ms-flexbox;
+ display:-webkit-box;
+ display:flex;
+ -webkit-box-align: end;
+ -ms-flex-align: end;
+ align-items: flex-end;
+ margin-bottom: 0.75rem;
+ 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;
+ }
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+module.exports = function(Alt) {
+ return Alt.generateActions(
+ 'handleUpdateInput',
+ 'handleAddProjectItem',
+ 'handleRemoveProjectItem',
+ 'handleUpdateProjectRole',
+ 'viewProject',
+ 'editPlatform',
+ 'handleCancelEdit',
+ 'handleCloseProjectPanel',
+ 'handleHideColumns',
+ 'handleSelectedUser',
+ 'handleSelectedDomain',
+ 'handleSelectedRole',
+ 'handleAddUser',
+ 'handleRemoveUserFromProject',
+ 'getProjectsSuccess',
+ 'getPlatformSuccess',
+ 'getPlatformRoleUsersSuccess',
+ 'getProjectsNotification',
+ 'handleDisabledChange',
+ 'handlePlatformRoleUpdate',
+ 'handleAddProject',
+ 'handleCreateProject',
+ 'handleUpdateProject',
+ 'handleUpdateSelectedUser',
+ 'handleUpdateUserRoleInProject',
+ 'handleToggleUserRoleInProject',
+ 'addRoleToUserInProject',
+ 'handleRemoveRoleFromUserInProject',
+ 'updatePlatformSuccess',
+ 'createProjectSuccess',
+ 'deleteProjectSuccess'
+ );
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import $ from 'jquery';
+var Utils = require('utils/utils.js');
+let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
+let HOST = API_SERVER;
+let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
+let DEV_MODE = require('utils/rw.js').getSearchParams(window.location).dev_mode || false;
+
+if (DEV_MODE) {
+ HOST = window.location.protocol + '//' + window.location.hostname;
+}
+
+
+
+module.exports = function(Alt) {
+ return {
+
+ getUsers: {
+ remote: function() {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/user?api_server=${API_SERVER}`,
+ type: 'GET',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data.user);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error retrieving the resource orchestrator information.'
+ }),
+ success: Alt.actions.global.getPlatformRoleUsersSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ getPlatform: {
+ remote: function() {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/platform?api_server=${API_SERVER}`,
+ type: 'GET',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data.platform);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error retrieving the resource orchestrator information.'
+ }),
+ success: Alt.actions.global.getPlatformSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ updatePlatform: {
+ remote: function(state, project) {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/platform?api_server=${API_SERVER}`,
+ type: 'PUT',
+ data: project,
+ dataType: 'json',
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error updating the project.'
+ }),
+ success: Alt.actions.global.updatePlatformSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ }
+ }
+}
+
+function interceptResponse (responses) {
+ return function(data, action, args) {
+ if(responses.hasOwnProperty(data)) {
+ return {
+ type: data,
+ msg: responses[data]
+ }
+ } else {
+ return data;
+ }
+ }
+}
+
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import PlatformRoleManagementActions from './platformRoleManagementActions.js';
+import PlatformRoleManagementSource from './platformRoleManagementSource.js';
+import _ from 'lodash';
+export default class PlatformRoleManagementStore {
+ constructor() {
+ this.actions = PlatformRoleManagementActions(this.alt);
+ this.bindActions(this.actions);
+ this.registerAsync(PlatformRoleManagementSource);
+ this.projects = [];
+ this['name'] = '';
+ this['description'] = 'Some Description';
+ this.platformUsers = [];
+ this.selectedUser = null;
+ this.selectedRole = null;
+ this.selectedDomain = null;
+ this.roles = ['rw-rbac-platform:super-admin', 'rw-rbac-platform:platform-admin', 'rw-rbac-platform:platform-oper'
+ // 'some_other_role', 'yet_another_role', 'operator_role', 'some_other_role', 'yet_another_role'
+ ];
+ this.users = [];
+ this.domains = [];
+ 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
+ });
+ }
+ editPlatform(isReadOnly) {
+ let state = _.merge({
+ isEdit: true,
+ isReadOnly: isReadOnly,
+ }, {
+ 'platformUsers': this.cachedUsers
+ });
+ this.setState(state)
+ }
+
+ handleCancelEdit() {
+
+ }
+ handleCloseProjectPanel() {
+ this.setState({
+ projectOpen: false,
+ isEdit: false,
+ isReadOnly: true
+ })
+ }
+ handleHideColumns(e) {
+ if(this.projectOpen && e.currentTarget.classList.contains('hideColumns')) {
+ this.setState({
+ hideColumns: true
+ })
+ } else {
+ this.setState({
+ hideColumns: false
+ })
+ }
+ }
+ handleDisabledChange(isDisabled){
+ this.setState({
+ disabled: isDisabled
+ })
+ }
+ handlePlatformRoleUpdate(data){
+ let platform_role = data[0];
+ let checked = data[1];
+ let platformRoles = this.platformRoles;
+ platformRoles[platform_role] = checked;
+ this.setState({
+ platformRoles
+ })
+ }
+ handleSelectedUser(event) {
+ this.setState({
+ selectedUser: JSON.parse(event.currentTarget.value)
+ })
+ }
+
+ handleSelectedRole(event) {
+ this.setState({
+ selectedRole: JSON.parse(event.currentTarget.value)
+ })
+ }
+ resetProject() {
+ let name = '';
+ let description = '';
+ return {
+ 'name' : name,
+ 'description' : description
+ }
+ }
+ handleAddProject() {
+ this.setState(_.merge( this.resetProject() ,
+ {
+ isEdit: false,
+ projectOpen: true,
+ activeIndex: null,
+ isReadOnly: false,
+ platformUsers: []
+ }
+ ))
+ }
+
+ handleUpdateSelectedUser(user) {
+ this.setState({
+ selectedUser: JSON.parse(user)
+ });
+ }
+ handleSelectedDomain(event) {
+ let domain = JSON.parse(event.target.value);
+ this.setState({
+ selectedDomain: domain
+ });
+ }
+ handleAddUser() {
+ let u = JSON.parse(this.selectedUser);
+ let r = this.selectedRole;
+ let platformUsers = this.platformUsers;
+ console.log('adding user')
+ platformUsers.push({
+ 'user-name': u['user-name'],
+ 'user-domain': u['user-domain'],
+ "role":[{
+ "role": r
+ }
+ ]
+ })
+ this.setState({platformUsers, selectedUser: null})
+ }
+ handleToggleUserRoleInProject(data) {
+ let self = this;
+ let {userIndex, roleIndex, checked} = data;
+ let platformUsers = this.platformUsers;
+ let selectedRole = self.roles[roleIndex];
+ if(checked) {
+ if(!platformUsers[userIndex].role) platformUsers[userIndex].role = [];
+ platformUsers[userIndex].role.push({
+ role: selectedRole
+ })
+ } else {
+ let role = platformUsers[userIndex].role;
+ platformUsers[userIndex].role.splice(_.findIndex(role, function(r) { return r.role == selectedRole; }), 1)
+ }
+ self.setState({platformUsers});
+
+ }
+ handleUpdateUserRoleInProject(data) {
+ let {userIndex, roleIndex, value} = data;
+ let platformUsers = this.platformUsers;
+ platformUsers[userIndex].role[roleIndex].role = value;
+
+ }
+ addRoleToUserInProject(userIndex) {
+ let platformUsers = this.platformUsers;
+ if(!platformUsers[userIndex].role) {
+ platformUsers[userIndex].role = [];
+ }
+ platformUsers[userIndex].role.push({
+ 'role': null
+ });
+ this.setState({
+ platformUsers
+ })
+ }
+ handleRemoveRoleFromUserInProject (data) {
+ let {userIndex, roleIndex} = data;
+ let platformUsers = this.platformUsers;
+ platformUsers[userIndex].role.splice(roleIndex, 1);
+ this.setState({
+ platformUsers
+ })
+ }
+ handleRemoveUserFromProject (userIndex) {
+ let platformUsers = this.platformUsers;
+ platformUsers.splice(userIndex, 1);
+ this.setState({
+ platformUsers
+ })
+ }
+ getProjectsSuccess(projects) {
+ this.alt.actions.global.hideScreenLoader.defer();
+ this.setState({projects: projects});
+ }
+ getPlatformSuccess(platform) {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let platformUsers = platform && platform.user || [];
+ let state = _.merge({
+ platform: platform,
+ projectOpen: true,
+ isEdit: true,
+ isReadOnly: true,
+ platformUsers: platformUsers,
+ cachedUsers: platformUsers
+ });
+ this.setState(state)
+ }
+ getPlatformRoleUsersSuccess(users) {
+ console.log(users)
+ this.alt.actions.global.hideScreenLoader.defer();
+ let domains = users && users.reduce(function(arr, u) {
+ if (arr.indexOf(u['user-domain']) == -1) {
+ arr.push(u['user-domain']);
+ return arr;
+ } else {
+ return arr
+ }
+ }, []);
+ this.setState({users, domains, selectedDomain: domains[0]});
+ }
+ updatePlatformSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let platformUsers = this.platformUsers;
+ this.setState({
+ platformUsers,
+ cachedUsers: platformUsers,
+ 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);
+ }
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import AppHeader from 'widgets/header/header.jsx';
+import UserProfileStore from './userProfileStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import 'style/layout.scss';
+import '../dashboard/userMgmt.scss';
+import { Panel, PanelWrapper } from 'widgets/panel/panel';
+
+
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import Input from 'widgets/form_controls/input.jsx';
+import Button, { ButtonGroup } from 'widgets/button/sq-button.jsx';
+import SelectOption from 'widgets/form_controls/selectOption.jsx';
+import 'widgets/form_controls/formControls.scss';
+import imgAdd from '../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../node_modules/open-iconic/svg/trash.svg';
+
+class UserProfileDashboard extends React.Component {
+ constructor(props) {
+ super(props);
+ this.Store = this.props.flux.stores.hasOwnProperty('UserProfileStore') ? this.props.flux.stores.UserProfileStore : this.props.flux.createStore(UserProfileStore);
+ this.state = this.Store.getState();
+ this.actions = this.state.actions;
+
+ }
+ componentDidUpdate() {
+ let self = this;
+ ReactDOM.findDOMNode(this.UserList).addEventListener('transitionend', this.onTransitionEnd, false);
+ setTimeout(function () {
+ let element = self[`user-ref-${self.state.activeIndex}`]
+ element && !isElementInView(element) && element.scrollIntoView({ block: 'end', behavior: 'smooth' });
+ })
+ }
+ componentWillMount() {
+ this.Store.listen(this.updateState);
+ }
+ componentWillUnmount() {
+ this.Store.unlisten(this.updateState);
+ }
+ updateState = (state) => {
+ this.setState(state);
+ }
+ updateInput = (key, e) => {
+ let property = key;
+ this.actions.handleUpdateInput({
+ [property]: e.target.value
+ })
+ }
+ disabledChange = (e) => {
+ this.actions.handleDisabledChange(e.target.checked);
+ }
+ platformChange = (platformRole, e) => {
+ this.actions.handlePlatformRoleUpdate(platformRole, e.currentTarget.checked);
+ }
+ addProjectRole = (e) => {
+ this.actions.handleAddProjectItem();
+ }
+ removeProjectRole = (i, e) => {
+ this.actions.handleRemoveProjectItem(i);
+ }
+ updateProjectRole = (i, e) => {
+ this.actions.handleUpdateProjectRole(i, e)
+ }
+ addUser = () => {
+ this.actions.handleAddUser();
+ }
+ viewUser = (un, index) => {
+ this.actions.viewUser(un, index);
+ }
+ editUser = () => {
+ this.actions.editUser(false);
+ }
+ cancelEditUser = () => {
+ this.actions.editUser(true)
+ }
+ osePanel = () => {
+ this.actions.handleCloseUserPanel();
+ }
+ // updateUser = (e) => {
+ // e.preventDefault();
+ // e.stopPropagation();
+
+ // this.Store.updateUser();
+ // }
+ deleteUser = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ this.Store.deleteUser({
+ 'user-name': this.state['user-name'],
+ 'user-domain': this.state['user-domain']
+ });
+ }
+ createUser = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (this.state['new-password'] != this.state['confirm-password']) {
+ this.props.actions.showNotification('Passwords do not match')
+ } else {
+ this.Store.createUser({
+ 'user-name': this.state['user-name'],
+ 'user-domain': this.state['user-domain'],
+ 'password': this.state['new-password']
+ // 'confirm-password': this.state['confirm-password']
+ });
+ }
+ }
+ updateUser = (e) => {
+ let self = this;
+ e.preventDefault();
+ e.stopPropagation();
+ let validatedPasswords = validatePasswordFields(this.state);
+ if (validatedPasswords) {
+ this.Store.updateUser(_.merge({
+ 'user-name': this.context.userProfile.userId,
+ 'user-domain': this.state['user-domain'],
+ 'password': this.state['new-password'],
+ 'ui-state': this.context.userProfile.data['ui-state']
+ }));
+ }
+ function validatePasswordFields(state) {
+ let newOne = state['new-password'];
+ let confirmOne = state['confirm-password'];
+ if (!newOne || !confirmOne) {
+ self.props.actions.showNotification('Please fill in all fields.');
+ return false;
+ }
+ if (newOne != confirmOne) {
+ self.props.actions.showNotification('Passwords do not match');
+ return false;
+ }
+ return {
+ 'new-password': newOne,
+ 'confirm-password': confirmOne
+ }
+ }
+ }
+ evaluateSubmit = (e) => {
+ if (e.keyCode == 13) {
+ if (this.state.isEdit) {
+ this.updateUser(e);
+ } else {
+ this.createUser(e);
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+ onTransitionEnd = (e) => {
+ this.actions.handleHideColumns(e);
+ console.log('transition end')
+ }
+ disableChange = (e) => {
+ let value = e.target.value;
+ value = value.toUpperCase();
+ if (value == "TRUE") {
+ value = true;
+ } else {
+ value = false;
+ }
+ console.log(value)
+ }
+ render() {
+
+ let self = this;
+ const User = this.context.userProfile || {};
+ let html;
+ let props = this.props;
+ let state = this.state;
+ let passwordSectionHTML = null;
+ let formButtonsHTML = (
+ <ButtonGroup className="buttonGroup">
+ <Button label="EDIT" type="submit" onClick={this.editUser} />
+ </ButtonGroup>
+ );
+ passwordSectionHTML = (
+ (
+ <FormSection title="PASSWORD CHANGE">
+ <Input label="NEW PASSWORD" type="password" value={state['new-password']} onChange={this.updateInput.bind(null, 'new-password')} />
+ <Input label="REPEAT NEW PASSWORD" type="password" value={state['confirm-password']} onChange={this.updateInput.bind(null, 'confirm-password')} />
+ </FormSection>
+ )
+ );
+ formButtonsHTML = (
+ <ButtonGroup className="buttonGroup">
+ <Button label="Update" type="submit" onClick={this.updateUser} />
+ </ButtonGroup>
+ )
+
+ html = (
+ <PanelWrapper column>
+ <PanelWrapper className={`row userManagement ${!this.state.userOpen ? 'userList-open' : ''}`} style={{ 'flexDirection': 'row' }} >
+ <PanelWrapper ref={(div) => { this.UserList = div }} className={`column userList expanded hideColumns`}>
+ <Panel title={User.userId} style={{ marginBottom: 0 }} no-corners>
+ <FormSection title="USER INFO">
+ <table className="userProfile-table">
+ <thead>
+ <tr>
+ <td>Project</td>
+ <td>Role</td>
+ </tr>
+ </thead>
+ <tbody>
+ {
+ User.data && User.data.projectId && User.data.projectId.map((p, i) => {
+ let project = User.data.project[p];
+ let projectConfig = project && project.data['project-config'];
+ let userRoles = [];
+ return (
+ <tr key={i}>
+ <td>
+ {p}
+ </td>
+ <td>
+ {
+ project && Object.keys(project.role).map(function (k, i) {
+ return <div key={i}>{k}</div>
+ })
+ }
+ </td>
+ </tr>
+ )
+ })
+ }
+ </tbody>
+ </table>
+ </FormSection>
+ {passwordSectionHTML}
+
+ </Panel>
+ <div className="buttonSection">
+ {formButtonsHTML}
+ </div>
+ </PanelWrapper>
+
+ </PanelWrapper>
+ </PanelWrapper>
+ );
+ return html;
+ }
+}
+// onClick={this.Store.update.bind(null, Account)}
+UserProfileDashboard.contextTypes = {
+ router: React.PropTypes.object,
+ userProfile: React.PropTypes.object
+};
+
+UserProfileDashboard.defaultProps = {
+ userList: [],
+ selectedUser: {}
+}
+
+export default SkyquakeComponent(UserProfileDashboard);
+
+
+function isElementInView(el) {
+ var rect = el && el.getBoundingClientRect() || {};
+
+ return (
+ rect.top >= 0 &&
+ rect.left >= 0 &&
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
+ );
+}
+
+
+// isReadOnly={state.isReadOnly} disabled={state.disabled} onChange={this.disableChange}
+
+class isDisabled extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+ render() {
+ let props = this.props;
+ return (<div />)
+ }
+}
+
+/**
+ * AddItemFn:
+ */
+class InputCollection extends React.Component {
+ constructor(props) {
+ super(props);
+ this.collection = props.collection;
+ }
+ buildTextInput(onChange, v, i) {
+ return (
+ <Input
+ readonly={this.props.readonly}
+ style={{ flex: '1 1' }}
+ key={i}
+ value={v}
+ onChange={onChange.bind(null, i)}
+ />
+ )
+ }
+ buildSelectOption(initial, options, onChange, v, i) {
+ return (
+ <SelectOption
+ readonly={this.props.readonly}
+ key={`${i}-${v.replace(' ', '_')}`}
+ intial={initial}
+ defaultValue={v}
+ options={options}
+ onChange={onChange.bind(null, i)}
+ />
+ );
+ }
+ showInput() {
+
+ }
+ render() {
+ const props = this.props;
+ let inputType;
+ let className = "InputCollection";
+ if (props.className) {
+ className = `${className} ${props.className}`;
+ }
+ if (props.type == 'select') {
+ inputType = this.buildSelectOption.bind(this, props.initial, props.options, props.onChange);
+ } else {
+ inputType = this.buildTextInput.bind(this, props.onChange)
+ }
+ let html = (
+ <div className="InputCollection-wrapper">
+ {props.collection.map((v, i) => {
+ return (
+ <div key={i} className={className} >
+ {inputType(v, i)}
+ {
+ props.readonly ? null : <span onClick={props.RemoveItemFn.bind(null, i)} className="removeInput"><img src={imgRemove} />Remove</span>}
+ </div>
+ )
+ })}
+ {props.readonly ? null : <span onClick={props.AddItemFn} className="addInput"><img src={imgAdd} />Add</span>}
+ </div>
+ );
+ return html;
+ }
+}
+
+InputCollection.defaultProps = {
+ input: Input,
+ collection: [],
+ onChange: function (i, e) {
+ console.log(`
+ Updating with: ${e.target.value}
+ At index of: ${i}
+ `)
+ },
+ AddItemFn: function (e) {
+ console.log(`Adding a new item to collection`)
+ },
+ RemoveItemFn: function (i, e) {
+ console.log(`Removing item from collection at index of: ${i}`)
+ }
+}
+
+class FormSection extends React.Component {
+ render() {
+ let className = 'FormSection ' + this.props.className;
+ let html = (
+ <div
+ style={this.props.style}
+ className={className}
+ >
+ <div className="FormSection-title">
+ {this.props.title}
+ </div>
+ <div className="FormSection-body">
+ {this.props.children}
+ </div>
+ </div>
+ );
+ return html;
+ }
+}
+
+FormSection.defaultProps = {
+ className: ''
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+module.exports = function(Alt) {
+ return Alt.generateActions(
+ 'handleUpdateInput',
+ 'handleAddProjectItem',
+ 'handleRemoveProjectItem',
+ 'handleUpdateProjectRole',
+ 'viewUser',
+ 'editUser',
+ 'handleCloseUserPanel',
+ 'handleHideColumns',
+ 'getUsersNotification',
+ 'handleDisabledChange',
+ 'handlePlatformRoleUpdate',
+ 'handleUpdateUser',
+ 'updateUserSuccess'
+ );
+}
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import $ from 'jquery';
+var Utils = require('utils/utils.js');
+let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
+let HOST = API_SERVER;
+let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
+let DEV_MODE = require('utils/rw.js').getSearchParams(window.location).dev_mode || false;
+
+if (DEV_MODE) {
+ HOST = window.location.protocol + '//' + window.location.hostname;
+}
+
+
+
+
+module.exports = function(Alt) {
+ return {
+ updateUser: {
+ remote: function(state, user) {
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/user?api_server=${API_SERVER}`,
+ type: 'PUT',
+ data: user,
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error updating the user.'
+ }),
+ success: Alt.actions.global.updateUserSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ },
+ createUser: {
+ remote: function(state, user) {
+
+ return new Promise(function(resolve, reject) {
+ $.ajax({
+ url: `/user?api_server=${API_SERVER}`,
+ type: 'POST',
+ data: user,
+ beforeSend: Utils.addAuthorizationStub,
+ success: function(data, textStatus, jqXHR) {
+ resolve(data);
+ }
+ }).fail(function(xhr){
+ //Authentication and the handling of fail states should be wrapped up into a connection class.
+ Utils.checkAuthentication(xhr.status);
+ let msg = xhr.responseText;
+ if(xhr.errorMessage) {
+ msg = xhr.errorMessage
+ }
+ reject(msg);
+ });
+ });
+ },
+ interceptResponse: interceptResponse({
+ 'error': 'There was an error updating the account.'
+ }),
+ success: Alt.actions.global.createUserSuccess,
+ loading: Alt.actions.global.showScreenLoader,
+ error: Alt.actions.global.handleServerReportedError
+ }
+ }
+}
+
+function interceptResponse (responses) {
+ return function(data, action, args) {
+ if(responses.hasOwnProperty(data)) {
+ return {
+ type: data,
+ msg: responses[data]
+ }
+ } else {
+ return data;
+ }
+ }
+}
+
--- /dev/null
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import UserProfileActions from './userProfileActions.js';
+import UserProfileSource from './userProfileSource.js';
+import _ from 'lodash';
+export default class UserProfileStore {
+ constructor() {
+ this.actions = UserProfileActions(this.alt);
+ this.bindActions(this.actions);
+ this.registerAsync(UserProfileSource);
+ this.users = [];
+ this['user-name'] = '';
+ this['user-domain'] = 'system';
+ this.disabled = false;
+ this.roles = ['rw-project:project-admin', 'rw-project:project-oper', 'rw-project:project-create'
+ ];
+ this.currentPassword = '';
+ this['old-password'] = '';
+ this['new-password'] = '';
+ this['confirm-password'] = '';
+
+ this.activeIndex = null;
+ this.isReadOnly = true;
+ this.userOpen = false;
+ this.hideColumns = false;
+ this.isEdit = false;
+ // this.exportPublicMethods({})
+ }
+ /**
+ * [handleFieldUpdate description]
+ * @param {Object} data {
+ * [store_property] : [value]
+ * }
+ * @return {[type]} [description]
+ */
+ handleUpdateInput(data) {
+ this.setState(data);
+ }
+ handleAddProjectItem(item) {
+ let projectRoles = this.projectRoles;
+ projectRoles.push('');
+ this.setState({projectRoles});
+ }
+ handleRemoveProjectItem(i) {
+ let projectRoles = this.projectRoles;
+ projectRoles.splice(i, 1);
+ console.log('Removing', projectRoles)
+ this.setState({projectRoles});
+ }
+ handleUpdateProjectRole(data) {
+ let i = data[0];
+ let e = data[1];
+ let projectRoles = this.projectRoles
+ projectRoles[i] = JSON.parse(e.currentTarget.value);
+ this.setState({
+ projectRoles
+ });
+ }
+ viewUser(data) {
+ let user = data[0];
+ let userIndex = data[1];
+
+ let ActiveUser = {
+ 'user-name': user['user-name'],
+ 'user-domain': user['user-domain'],
+ platformRoles: user.platformRoles || this.platformRoles,
+ disabled: user.disabled || this.disabled,
+ projectRoles: user.projectRoles || this.projectRoles
+ }
+ let state = _.merge({
+ activeIndex: userIndex,
+ userOpen: true,
+ isEdit: true,
+ isReadOnly: true
+ }, ActiveUser);
+ this.setState(state)
+ }
+ editUser(isEdit) {
+ this.setState({
+ isReadOnly: isEdit
+ })
+ }
+ handleCloseUserPanel() {
+ this.setState({
+ userOpen: false,
+ isEdit: false,
+ isReadOnly: true
+ })
+ }
+ handleHideColumns(e) {
+ if(this.userOpen && e.currentTarget.classList.contains('hideColumns')) {
+ this.setState({
+ hideColumns: true
+ })
+ } else {
+ this.setState({
+ hideColumns: false
+ })
+ }
+ }
+ handleDisabledChange(isDisabled){
+ this.setState({
+ disabled: isDisabled
+ })
+ }
+ handlePlatformRoleUpdate(data){
+ let platform_role = data[0];
+ let checked = data[1];
+ let platformRoles = this.platformRoles;
+ platformRoles[platform_role] = checked;
+ this.setState({
+ platformRoles
+ })
+ }
+ resetUser() {
+ let username = '';
+ let domain = 'system';
+ let disabled = false;
+ let platformRoles = {
+ super_admin: false,
+ platform_admin: false,
+ platform_oper: false
+ };
+ let projectRoles = [];
+ let currentPassword = '';
+ let oldPassword = '';
+ let newPassword = '';
+ let confirmPassword = '';
+ return {
+ 'user-name' : username,
+ 'user-domain' : domain,
+ disabled,
+ platformRoles,
+ projectRoles,
+ currentPassword,
+ 'old-password': oldPassword,
+ 'new-password': newPassword,
+ 'confirm-password': confirmPassword
+ }
+ }
+ resetPassword() {
+ let currentPassword = '';
+ let oldPassword = '';
+ let newPassword = '';
+ let confirmPassword = '';
+ return {
+ currentPassword,
+ 'old-password': oldPassword,
+ 'new-password': newPassword,
+ 'confirm-password': confirmPassword
+ }
+ }
+ handleAddUser() {
+ this.setState(_.merge( this.resetUser() ,
+ {
+ isEdit: false,
+ userOpen: true,
+ activeIndex: null,
+ isReadOnly: false
+ }
+ ))
+ }
+ handleCreateUser() {
+
+ }
+ handleUpdateUser() {
+
+ }
+ updateUserSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let users = this.users || [];
+ users[this.activeIndex] = {
+ 'user-name': this['user-name'],
+ 'user-domain': this['user-domain'],
+ platformRoles: this.platformRoles,
+ disabled: this.disabled,
+ projectRoles: this.projectRoles
+ }
+ this.setState({
+ users,
+ isEdit: true,
+ isReadOnly: true
+ })
+ }
+ deleteUserSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let users = this.users;
+ users.splice(this.activeIndex, 1);
+ this.setState({users, userOpen: false})
+ }
+ createUserSuccess() {
+ this.alt.actions.global.hideScreenLoader.defer();
+ let users = this.users || [];
+ users.push({
+ 'user-name': this['user-name'],
+ 'user-domain': this['user-domain'],
+ platformRoles: this.platformRoles,
+ disabled: this.disabled,
+ projectRoles: this.projectRoles,
+ });
+ let newState = {
+ users,
+ isEdit: true,
+ isReadOnly: true,
+ activeIndex: users.length - 1
+ };
+ _.merge(newState, this.resetPassword())
+ this.setState(newState);
+ }
+}
--- /dev/null
+/*
+ * 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 CompressionPlugin = require("compression-webpack-plugin");
+
+// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
+process.env.UV_THREADPOOL_SIZE=64;
+var htmlFilename = (process.argv.indexOf('--production-debug') !== -1) ? 'debug.html' : 'index.html';
+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: '../' + htmlFilename,
+ template: frameworkPath + '/plugin-index.html'
+ }),
+ ]
+};
+
+if (process.argv.indexOf('--optimize-minimize') !== -1) {
+ // we are going to output a gzip file in the production process
+ config.output.filename = "gzip-" + config.output.filename;
+ config.plugins.push(new webpack.DefinePlugin({ // <-- key to reducing React's size
+ 'process.env': {
+ 'NODE_ENV': JSON.stringify('production')
+ }
+ }));
+ config.plugins.push(new CompressionPlugin({
+ asset: "[path]", // overwrite js file with gz file
+ algorithm: "gzip",
+ test: /\.(js)$/
+ }));
+}
+
+module.exports = config;
--- /dev/null
+#!/bin/bash
+
+#
+# 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.
+#
+
+
+
+abort()
+{
+ echo >&2 '
+***************
+*** ABORTED ***
+***************
+'
+ echo "An error occurred. Exiting..." >&2
+ exit 1
+}
+
+trap 'abort' 0
+
+set -e
+
+# Add your script below....
+# If an error occurs, the abort() function will be called.
+#----------------------------------------------------------
+# change to the directory of this script
+THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+cd $THIS_DIR
+cd ..
+
+echo "NPM config"
+npm config ls
+echo "clean node_modules"
+rm -fr node_modules
+echo "Building RW.UI framework"
+npm install
+echo "RW.UI framework build... done"
+
+echo "Building RW.UI plugins"
+cd plugins
+for f in *; do
+ if [[ -d $f ]]; then
+ echo 'Building plugin '$f
+ cd $f
+ rm -fr node_modules
+ npm install
+ ./node_modules/.bin/webpack --progress --config webpack.production.config.js --bail
+ cd ..
+ echo 'Building plugin '$f' ... done'
+ fi
+done
+
+echo "Building RW.UI plugins... done"
+# Done!
+trap : 0
+
+echo >&2 '
+************
+*** DONE ***
+************
+'
+
+
--- /dev/null
+#!/bin/bash
+
+#
+# Copyright 2017 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.
+#
+
+abort()
+{
+ echo >&2 '
+***************
+*** ABORTED ***
+***************
+'
+ echo "An error occurred. Exiting..." >&2
+ exit 1
+}
+
+trap 'abort' 0
+
+set -e
+
+# Add your script below....
+# If an error occurs, the abort() function will be called.
+#----------------------------------------------------------
+CMAKE_BUILD=true
+
+# change to the directory of this script
+cd $PLUGIN_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=$CMAKE_BUILD ./node_modules/.bin/webpack --optimize-minimize --progress --config webpack.production.config.js --bail
+echo 'Packaging '$PLUGIN_NAME' using webpack... done'
+echo 'Packaging debug version of '$PLUGIN_NAME' using webpack'
+ui_plugin_cmake_build=$CMAKE_BUILD ./node_modules/.bin/webpack --progress --config webpack.production.config.js --production-debug --bail
+echo 'Packaging '$PLUGIN_NAME' using webpack... done'
+echo 'Building plugin '$PLUGIN_NAME'... done'
+# Done!
+trap : 0
+
+echo >&2 '
+************
+*** DONE ***
+************
+'
\ No newline at end of file
if [[ -d $f ]]; then
echo 'Building plugin '$f
cd $f
- echo 'Fetching third-party node_modules for '$f
- npm install
- echo 'Fetching third-party node_modules for '$f'...done'
- echo 'Packaging '$f' using webpack'
- ./node_modules/.bin/webpack --optimize-minimize --progress --config webpack.production.config.js
- echo 'Packaging '$f' using webpack... done'
+ echo 'Run build script for '$f
+ ./scripts/build.sh
cd ..
echo 'Building plugin '$f'... done'
fi
--- /dev/null
+#!/bin/bash
+
+#
+# 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.
+#
+
+# Unpack tar archives for plugins. Maintain a timestamp.txt file for each, so
+# that tar archives are only unpacked if they have been changed.
+#
+# This script is needed to unpack the files as cpack cannot handle a lot of files.
+
+usage() {
+ echo "usage: $(basename ${BASH_SOURCE[0]})"
+}
+
+function extract_node_modules() {
+ tar xf node_modules.tar
+ touch timestamp.txt
+}
+
+# change to the directory of this script
+THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+
+cd $THIS_DIR
+echo "Handling plugin node modules"
+
+cd ../plugins
+for dir in */; do
+ echo "Checking plugin "${dir}" for newer node_modules"
+ cd ${dir}
+ if [ ! -f timestamp.txt ]; then
+ echo "timestamp file not found ... node_modules need to be expanded and timestamp needs to be touched"
+ extract_node_modules
+ else
+ echo "Checking if node_modules.tar has a newer timestamp than timestamp.txt"
+ if [[ node_modules.tar -nt timestamp.txt ]]; then
+ echo "node_modules.tar is newer than timestamp ... node modules need to be expanded and timestamp needs to be touched"
+ extract_node_modules
+ else
+ echo "node_modules.tar is older than timestamp ... nothing needs to be done"
+ fi
+ fi
+ cd ..
+ echo "Checking plugin "${dir}" for newer node_modules ...done"
+done
+
#
usage() {
- echo "usage: launch_ui.sh [--enable-https --keyfile-path=<keyfile_path> --certfile-path=<certfile-path>]"
+ echo "usage: launch_ui.sh --launchpad-address=<ip_or_fqdn> --idp-port-number=<port_number> [--enable-https --keyfile-path=<keyfile_path> --certfile-path=<certfile-path>]"
}
function handle_received_signal() {
exit
}
+# Gets the current hosts ip/fqdn. If the host is resolvable through dns, it
+# returns the fqdn else returns the ip address.
+get_host_address() {
+ if [[ -z $(hostname -d) ]]; then
+ # not resolvable via dns, use resolvable ip address
+ echo $(hostname --ip-address)
+ else
+ # use the fqdn
+ echo $(hostname --fqdn)
+ fi
+}
start_servers() {
cd $THIS_DIR
echo "Stopping any previous instances of Skyquake and API servers started with forever"
forever stopall
+ local launchpad_address=$(get_host_address)
- echo "Running Node.js Skyquake server. HTTPS Enabled: ${ENABLE_HTTPS}"
+ echo "Running Node.js Skyquake server. HTTPS Enabled: ${ENABLE_HTTPS}, Launchpad Address: ${launchpad_address}"
cd ..
if [ ! -z "${ENABLE_HTTPS}" ]; then
- forever start -a -l forever.log -o out.log -e err.log skyquake.js --enable-https --keyfile-path="${KEYFILE_PATH}" --certfile-path="${CERTFILE_PATH}"
+ forever start -a -l forever.log -o out.log -e err.log skyquake.js --enable-https --keyfile-path="${KEYFILE_PATH}" --certfile-path="${CERTFILE_PATH}" --launchpad-address="${LAUNCHPAD_ADDRESS}" --idp-port-number="${IDP_PORT_NUMBER}" --callback-address="${CALLBACK_ADDRESS}"
else
- forever start -a -l forever.log -o out.log -e err.log skyquake.js
+ forever start -a -l forever.log -o out.log -e err.log skyquake.js --launchpad-address="${LAUNCHPAD_ADDRESS}" --idp-port-number="${IDP_PORT_NUMBER}" --callback-address="${CALLBACK_ADDRESS}"
fi
}
-function extract_node_modules() {
- tar xf node_modules.tar
- touch timestamp.txt
-}
-
-function handle_plugin_node_modules() {
- cd $THIS_DIR
- echo "Handling plugin node modules"
-
- cd ../plugins
- for dir in */; do
- echo "Checking plugin "${dir}" for newer node_modules"
- cd ${dir}
- if [ ! -f timestamp.txt ]; then
- echo "timestamp file not found ... node_modules need to be expanded and timestamp needs to be touched"
- extract_node_modules
- else
- echo "Checking if node_modules.tar has a newer timestamp than timestamp.txt"
- if [[ node_modules.tar -nt timestamp.txt ]]; then
- echo "node_modules.tar is newer than timestamp ... node modules need to be expanded and timestamp needs to be touched"
- extract_node_modules
- else
- echo "node_modules.tar is older than timestamp ... nothing needs to be done"
- fi
- fi
- cd ..
- echo "Checking plugin "${dir}" for newer node_modules ...done"
- done
-}
-
-
# Begin work
for i in "$@"
do
CERTFILE_PATH="${i#*=}"
shift # past argument=value
;;
+ -l=*|--launchpad-address=*)
+ LAUNCHPAD_ADDRESS="${i#*=}"
+ shift # past argument=value
+ ;;
+ -p=*|--idp-port-number=*)
+ IDP_PORT_NUMBER="${i#*=}"
+ shift # past argument=value
+ ;;
+ -r=*|--callback-address=*)
+ CALLBACK_ADDRESS="${i#*=}"
+ shift # past argument=value
+ ;;
-h|--help)
usage
exit
# change to the directory of this script
THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
-# Call function to handle tarred node_modules as cpack+RPM cannot handle a lot of files
-handle_plugin_node_modules
+cd $THIS_DIR
+
+# Call script to handle tarred node_modules as cpack+RPM cannot handle a lot of files
+$THIS_DIR/handle_plugin_node_modules
# Call function to start web and API servers
start_servers
+
+
/*
*
* Copyright 2016 RIFT.IO Inc
// const Replay = require('replay');
var freePorts = [];
for (var i = 0; i < constants.SOCKET_POOL_LENGTH; i++) {
- freePorts[i] = constants.SOCKET_BASE_PORT + i;
+ freePorts[i] = constants.SOCKET_BASE_PORT + i;
};
if (cluster.isMaster && clusteredLaunch) {
console.log(cpu, 'CPUs found');
for (var i = 0; i < cpu; i ++) {
- var worker = cluster.fork();
- worker.on('message', function(msg) {
- if (msg && msg.getPort) {
- worker.send({
- port: freePorts.shift()
- });
- console.log('freePorts after shift for worker', this.process.pid, ':', freePorts);
- } else if (msg && msg.freePort) {
- freePorts.unshift(msg.port);
- console.log('freePorts after unshift of', msg.port, 'for worker', this.process.pid, ':', freePorts);
- }
- });
+ var worker = cluster.fork();
+ worker.on('message', function(msg) {
+ if (msg && msg.getPort) {
+ worker.send({
+ port: freePorts.shift()
+ });
+ console.log('freePorts after shift for worker', this.process.pid, ':', freePorts);
+ } else if (msg && msg.freePort) {
+ freePorts.unshift(msg.port);
+ console.log('freePorts after unshift of', msg.port, 'for worker', this.process.pid, ':', freePorts);
+ }
+ });
}
cluster.on('online', function(worker) {
console.log('worker ' + worker.process.pid + ' stopped');
});
} else {
- // Standard library imports
- var argv = require('minimist')(process.argv.slice(2));
- var pid = process.pid;
- var fs = require('fs');
- var https = require('https');
- var http = require('http');
- var express = require('express');
- var session = require('express-session');
- var cors = require('cors');
- var bodyParser = require('body-parser');
- var _ = require('lodash');
- var reload = require('require-reload')(require);
- var Sockets = require('./framework/core/api_utils/sockets.js');
-
- require('require-json');
-
- // SSL related configuration bootstrap
- var httpServer = null;
- var secureHttpServer = null;
-
- var httpsConfigured = false;
-
- var sslOptions = null;
-
- var apiServer = argv['api-server'] ? argv['api-server'] : 'localhost';
- var uploadServer = argv['upload-server'] ? argv['upload-server'] : null;
-
- try {
- if (argv['enable-https']) {
- var keyFilePath = argv['keyfile-path'];
- var certFilePath = argv['certfile-path'];
-
- sslOptions = {
- key: fs.readFileSync(keyFilePath),
- cert: fs.readFileSync(certFilePath)
- };
-
- httpsConfigured = true;
- }
- } catch (e) {
- console.log('HTTPS enabled but file paths missing/incorrect');
- process.exit(code = -1);
- }
-
- var app = express();
-
- app.use(session({
- secret: 'ritio rocks',
- resave: true,
- saveUninitialized: true
- }));
- app.use(bodyParser.json());
- app.use(cors());
- app.use(bodyParser.urlencoded({
- extended: true
- }));
-
- var socketManager = new Sockets();
- var socketConfig = {
- httpsConfigured: httpsConfigured
- };
-
- if (httpsConfigured) {
- socketConfig.sslOptions = sslOptions;
- }
-
- // Rift framework imports
- var constants = require('./framework/core/api_utils/constants');
- var skyquakeEmitter = require('./framework/core/modules/skyquakeEmitter');
- var navigation_routes = require('./framework/core/modules/routes/navigation');
- var socket_routes = require('./framework/core/modules/routes/sockets');
- var restconf_routes = require('./framework/core/modules/routes/restconf');
- var inactivity_routes = require('./framework/core/modules/routes/inactivity');
- var descriptor_routes = require('./framework/core/modules/routes/descriptorModel');
- var configuration_routes = require('./framework/core/modules/routes/configuration');
- var configurationAPI = require('./framework/core/modules/api/configuration');
- /**
- * Processing when a plugin is added or modified
- * @param {string} plugin_name - Name of the plugin
- */
- function onPluginAdded(plugin_name) {
- // Load plugin config
- var plugin_config = reload('./plugins/' + plugin_name + '/config.json');
-
- // Load all app's views
- app.use('/' + plugin_name, express.static('./plugins/' + plugin_name + '/' + plugin_config.root));
-
- // Load all app's routes
- app.use('/' + plugin_name, require('./plugins/' + plugin_name + '/routes'));
-
- // Publish navigation links
- if (plugin_config.routes && _.isArray(plugin_config.routes)) {
- skyquakeEmitter.emit('config_discoverer.navigation_discovered', plugin_name, plugin_config);
- }
-
- }
-
- /**
- * Start listening on a port
- * @param {string} port - Port to listen on
- * @param {object} httpServer - httpServer created with http(s).createServer
- */
- function startListening(port, httpServer) {
- var server = httpServer.listen(port, function () {
- var host = server.address().address;
-
- var port = server.address().port;
-
- console.log('Express server listening on port', port);
- });
- return server;
- }
-
- /**
- * Initialize skyquake
- */
- function init() {
- skyquakeEmitter.on('plugin_discoverer.plugin_discovered', onPluginAdded);
- skyquakeEmitter.on('plugin_discoverer.plugin_updated', onPluginAdded);
- }
-
- /**
- * Configure skyquake
- */
- function config() {
- // Conigure any globals
- process.env.NODE_TLS_REJECT_UNAUTHORIZED=0;
-
- // Configure navigation router
- app.use(navigation_routes);
-
- // Configure restconf router
- app.use(restconf_routes);
-
- //Configure inactivity route(s)
- app.use(inactivity_routes);
-
- // Configure global config with ssl enabled/disabled
- var globalConfig = {
- ssl_enabled: httpsConfigured,
- api_server: apiServer
- };
-
- if (uploadServer) {
- globalConfig.upload_server = uploadServer;
- }
-
- configurationAPI.globalConfiguration.update(globalConfig);
-
- // Configure configuration route(s)
- app.use(configuration_routes);
-
- //Configure descriptor route(s)
- app.use(descriptor_routes);
-
- // app.get('/testme', function(req, res) {
- // res.sendFile(__dirname + '/index.html');
- // });
-
- // Configure HTTP/HTTPS server and populate socketConfig.
- if (httpsConfigured) {
- console.log('HTTPS configured. Will create 2 servers');
- secureHttpServer = https.createServer(sslOptions, app);
- // Add redirection on SERVER_PORT
- httpServer = http.createServer(function(req, res) {
- var host = req.headers['host'];
- host = host.replace(/:\d+$/, ":" + constants.SECURE_SERVER_PORT);
-
- res.writeHead(301, { "Location": "https://" + host + req.url });
- res.end();
- });
-
- socketConfig.httpServer = secureHttpServer;
- } else {
- httpServer = http.createServer(app);
- socketConfig.httpServer = httpServer;
- }
-
- // Configure socket manager
- socketManager.configure(socketConfig);
-
- // Configure socket router
- socket_routes.routes(socketManager);
- app.use(socket_routes.router);
-
- // Serve multiplex-client
- app.get('/multiplex-client', function(req, res) {
- res.sendFile(__dirname + '/node_modules/websocket-multiplex/multiplex_client.js');
- });
-
- // handle requests for gzip'd files
- app.get('*gzip*', function (req, res, next) {
- res.set('Content-Encoding', 'gzip');
- next();
- });
-
- }
-
- /**
- * Run skyquake functionality
- */
- function run() {
-
- // Start plugin_discoverer
- var navigation_manager = require('./framework/core/modules/navigation_manager');
- var plugin_discoverer = require('./framework/core/modules/plugin_discoverer');
-
- // Initialize asynchronous modules
- navigation_manager.init();
- plugin_discoverer.init();
-
- // Configure asynchronous modules
- navigation_manager.config()
- plugin_discoverer.config({
- plugins_path: './plugins'
- });
-
- // Run asynchronous modules
- navigation_manager.run();
- plugin_discoverer.run();
+ // Standard library imports
+ require('require-json');
+ var argv = require('minimist')(process.argv.slice(2));
+ var pid = process.pid;
+ var fs = require('fs');
+ var https = require('https');
+ var http = require('http');
+ var express = require('express');
+ var session = require('express-session');
+ var cors = require('cors');
+ var lusca = require('lusca');
+ var bodyParser = require('body-parser');
+ var _ = require('lodash');
+ var reload = require('require-reload')(require);
+ var Sockets = require('./framework/core/api_utils/sockets.js');
+ var AuthorizationManager = require('./framework/core/api_utils/auth.js');
+ var utils = require('./framework/core/api_utils/utils.js');
+ var CSRFManager = require('./framework/core/api_utils/csrf.js');
+
+ // SSL related configuration bootstrap
+ var httpServer = null;
+ var secureHttpServer = null;
+
+ var httpsConfigured = false;
+
+ var sslOptions = null;
+
+ var apiServer = argv['api-server'] ? argv['api-server'] : 'localhost';
+ var apiServerProtocol = argv['api-server-protocol'] ? argv['api-server-protocol'] : 'https';
+ var uploadServer = argv['upload-server'] ? argv['upload-server'] : null;
+ var devDownloadServer = argv['dev-download-server'] ? argv['dev-download-server'] : null;
+
+ var launchpadAddress = argv['launchpad-address'] ? argv['launchpad-address'] : constants.LAUNCHPAD_ADDRESS;
+ var idpServerPortNumber = argv['idp-port-number'] ? argv['idp-port-number'] : constants.IDP_PORT_NUMBER;
+ var idpServerProtocol = argv['idp-server-protocol'] ? argv['idp-server-protocol'] : constants.IDP_SERVER_PROTOCOL;
+ var callbackServerProtocol = argv['callback-server-protocol'] ? argv['callback-server-protocol'] : constants.CALLBACK_SERVER_PROTOCOL;
+ var callbackPortNumber = argv['callback-port-number'] ? argv['callback-port-number'] : constants.CALLBACK_PORT_NUMBER;
+ var callbackAddress = argv['callback-address'] ? argv['callback-address'] : constants.CALLBACK_ADDRESS;
+
+ var devServerAddress = argv['dev-server-address'] ? argv['dev-server-address'] : null;
+
+ try {
+ if (argv['enable-https']) {
+ var keyFilePath = argv['keyfile-path'];
+ var certFilePath = argv['certfile-path'];
+
+ sslOptions = {
+ key: fs.readFileSync(keyFilePath),
+ cert: fs.readFileSync(certFilePath)
+ };
+
+ httpsConfigured = true;
+ }
+ } catch (e) {
+ console.log('HTTPS enabled but file paths missing/incorrect');
+ process.exit(code = -1);
+ }
+
+ var app = express();
+
+ app.set('views', __dirname + '/framework/core/views');
+ app.engine('html', require('ejs').renderFile);
+ app.set('view engine', 'ejs');
+
+ app.use(session({
+ secret: 'riftio rocks',
+ resave: false,
+ saveUninitialized: true
+ }));
+ // clickjack attach suppression
+ app.use(lusca.xframe('SAMEORIGIN')); // for older browsers
+ app.use(lusca.csp({ policy: { 'frame-ancestors': '\'none\'' }}));
+
+ app.use(bodyParser.json());
+ app.use(cors());
+ app.use(bodyParser.urlencoded({
+ extended: true
+ }));
+
+ var csrfTarget = (devServerAddress ? devServerAddress : launchpadAddress)
+
+ var csrfConfig = {
+ target: csrfTarget
+ }
+
+ CSRFManager.configure(csrfConfig);
+
+ var openidConfig = {
+ idpServerProtocol: idpServerProtocol,
+ idpServerAddress: launchpadAddress,
+ idpServerPortNumber: idpServerPortNumber,
+ callbackServerProtocol: callbackServerProtocol,
+ callbackAddress: callbackAddress,
+ callbackPortNumber: callbackPortNumber
+ }
+
+ var authManager = new AuthorizationManager(openidConfig);
+ var authConfig = {
+ app: app
+ };
+
+
+ var socketManager = new Sockets();
+ var socketConfig = {
+ httpsConfigured: httpsConfigured
+ };
+
+ if (httpsConfigured) {
+ socketConfig.sslOptions = sslOptions;
+ };
+
+ var sessionsConfig = {
+ authManager: authManager,
+ api_server: apiServer,
+ api_server_protocol: apiServerProtocol
+ }
+ // Rift framework imports
+ var constants = require('./framework/core/api_utils/constants');
+ var skyquakeEmitter = require('./framework/core/modules/skyquakeEmitter');
+ var auth_routes = require('./framework/core/modules/routes/auth');
+ var navigation_routes = require('./framework/core/modules/routes/navigation');
+ var socket_routes = require('./framework/core/modules/routes/sockets');
+ var restconf_routes = require('./framework/core/modules/routes/restconf');
+ var inactivity_routes = require('./framework/core/modules/routes/inactivity');
+ var descriptor_routes = require('./framework/core/modules/routes/descriptorModel');
+ var configuration_routes = require('./framework/core/modules/routes/configuration');
+ var configurationAPI = require('./framework/core/modules/api/configuration');
+ var userManagement_routes = require('./framework/core/modules/routes/userManagement');
+ var projectManagement_routes = require('./framework/core/modules/routes/projectManagement');
+ var session_routes = require('./framework/core/modules/routes/sessions');
+ var schemaAPI = require('./framework/core/modules/api/schemaAPI');
+ var modelAPI = require('./framework/core/modules/api/modelAPI');
+ var appConfigAPI = require('./framework/core/modules/api/appConfigAPI');
+
+ schemaAPI.init();
+ modelAPI.init();
+ appConfigAPI.init();
+ /**
+ * Processing when a plugin is added or modified
+ * @param {string} plugin_name - Name of the plugin
+ */
+ function onPluginAdded(plugin_name) {
+ // Load plugin config
+ var plugin_config = reload('./plugins/' + plugin_name + '/config.json');
+
+ // Load all app's views
+ app.use('/' + plugin_name, express.static('./plugins/' + plugin_name + '/' + plugin_config.root));
+
+ // Load all app's routes
+ app.use('/' + plugin_name, require('./plugins/' + plugin_name + '/routes'));
+
+ // Publish navigation links
+ if (plugin_config.routes && _.isArray(plugin_config.routes)) {
+ skyquakeEmitter.emit('config_discoverer.navigation_discovered', plugin_name, plugin_config);
+ }
+
+ }
+
+ /**
+ * Serve jquery
+ */
+ app.use('/jquery', express.static('./node_modules/jquery/dist/jquery.min.js'));
+ /**
+ * Serve images
+ */
+ app.use('/img', express.static('./framework/style/img'));
+
+ /**
+ * Start listening on a port
+ * @param {string} port - Port to listen on
+ * @param {object} httpServer - httpServer created with http(s).createServer
+ */
+ function startListening(port, httpServer) {
+ var server = httpServer.listen(port, function () {
+ var host = server.address().address;
+
+ var port = server.address().port;
+
+ console.log('Express server listening on port', port);
+ });
+ return server;
+ }
+
+ /**
+ * Initialize skyquake
+ */
+ function init() {
+ skyquakeEmitter.on('plugin_discoverer.plugin_discovered', onPluginAdded);
+ skyquakeEmitter.on('plugin_discoverer.plugin_updated', onPluginAdded);
+ }
+
+ /**
+ * Configure skyquake
+ */
+ function config() {
+ // Conigure any globals
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED=0;
+
+ // Configure auth manager
+ authManager.configure(authConfig);
+
+ // Configure auth router
+ auth_routes.routes(authManager);
+ app.use(auth_routes.router);
+
+ //Configure session route(s)
+ session_routes.routes(sessionsConfig);
+ app.use(session_routes.router);
+
+ // Configure navigation router
+ app.use(navigation_routes);
+
+ // Configure restconf router
+ app.use(restconf_routes);
+
+ //Configure inactivity route(s)
+ app.use(inactivity_routes);
+
+ // Configure global config with ssl enabled/disabled
+ var globalConfig = {
+ ssl_enabled: httpsConfigured,
+ api_server: apiServer,
+ api_server_protocol: apiServerProtocol,
+ api_server_port_number: constants.LAUNCHPAD_PORT,
+ idp_server_address: launchpadAddress,
+ idp_server_protocol: idpServerProtocol,
+ idp_server_port_number: idpServerPortNumber,
+ csrf_target: csrfTarget
+ };
+
+ if (uploadServer) {
+ globalConfig.upload_server = uploadServer;
+ }
+ if (devDownloadServer) {
+ globalConfig.dev_download_server = devDownloadServer;
+ }
+
+ configurationAPI.globalConfiguration.update(globalConfig);
+
+ // Configure configuration route(s)
+ app.use(configuration_routes);
+
+ // Configure schema api
+ app.use(schemaAPI.getRouter());
+
+ // Configure model api
+ app.use(modelAPI.getRouter());
+
+ // Configure config api
+ app.use(appConfigAPI.getRouter());
+
+ //Configure descriptor route(s)
+ app.use(descriptor_routes);
+
+ //Configure user management route(s)
+ app.use(userManagement_routes);
+
+ //Configure project management route(s)
+ app.use(projectManagement_routes);
+
+ // app.get('/testme', function(req, res) {
+ // res.sendFile(__dirname + '/index.html');
+ // });
+
+ // Configure HTTP/HTTPS server and populate socketConfig.
+ if (httpsConfigured) {
+ console.log('HTTPS configured. Will create 2 servers');
+ secureHttpServer = https.createServer(sslOptions, app);
+ // Add redirection on SERVER_PORT
+ httpServer = http.createServer(function(req, res) {
+ var host = req.headers['host'];
+ host = host.replace(/:\d+$/, ":" + constants.SECURE_SERVER_PORT);
+
+ res.writeHead(301, { "Location": "https://" + host + req.url });
+ res.end();
+ });
+
+ socketConfig.httpServer = secureHttpServer;
+ } else {
+ httpServer = http.createServer(app);
+ socketConfig.httpServer = httpServer;
+ }
+
+ // Configure socket manager
+ socketManager.configure(socketConfig);
+
+ // Configure socket router
+ socket_routes.routes(socketManager);
+ app.use(socket_routes.router);
+
+ // Serve multiplex-client
+ app.get('/multiplex-client', function(req, res) {
+ res.sendFile(__dirname + '/node_modules/websocket-multiplex/multiplex_client.js');
+ });
+
+ // handle requests for gzip'd files
+ app.get('*gzip*', function (req, res, next) {
+ res.set('Content-Encoding', 'gzip');
+ next();
+ });
+
+ }
+
+ /**
+ * Run skyquake functionality
+ */
+ function run() {
+
+ // Start plugin_discoverer
+ var navigation_manager = require('./framework/core/modules/navigation_manager');
+ var plugin_discoverer = require('./framework/core/modules/plugin_discoverer');
+
+ // Initialize asynchronous modules
+ navigation_manager.init();
+ plugin_discoverer.init();
+
+ // Configure asynchronous modules
+ navigation_manager.config()
+ plugin_discoverer.config({
+ plugins_path: './plugins'
+ });
+
+ // Run asynchronous modules
+ navigation_manager.run();
+ plugin_discoverer.run();
+
+
+ // Server start
+ if (httpsConfigured) {
+ console.log('HTTPS configured. Will start 2 servers');
+ // Start listening on SECURE_SERVER_PORT (8443)
+ var secureServer = startListening(constants.SECURE_SERVER_PORT, secureHttpServer);
+ }
+ // Start listening on SERVER_PORT (8000)
+ var server = startListening(constants.SERVER_PORT, httpServer);
+
+ }
- // Server start
- if (httpsConfigured) {
- console.log('HTTPS configured. Will start 2 servers');
- // Start listening on SECURE_SERVER_PORT (8443)
- var secureServer = startListening(constants.SECURE_SERVER_PORT, secureHttpServer);
- }
- // Start listening on SERVER_PORT (8000)
- var server = startListening(constants.SERVER_PORT, httpServer);
-
- }
+ init();
- init();
-
- config();
+ config();
- run();
+ run();
}
--- /dev/null
+import React from 'react';
+import { storiesOf, action } from '@kadira/storybook';
+import reactToJsx from 'react-to-jsx';
+
+import FileManager from '../../plugins/composer/src/src/components/filemanager/FileManager.jsx';
+
+import '../../node_modules/open-iconic/font/css/open-iconic.css';
+import 'style/base.scss';
+
+
+storiesOf('File Manager', module)
+ .add('File Manager', () => {
+ return (
+ <div style={{backgroundColor: '#e5e5e5',height:'100%', position:'absolute', top: 0, left: 0, right: 0, bottom:0}}>
+ <FileManager/>
+ </div>
+ )
+ })
+