NOTICKET: Merging OSM/master to OSM/projects
authorKIRAN KASHALKAR <kiran.kashalkar@riftio.com>
Thu, 20 Apr 2017 13:18:13 +0000 (09:18 -0400)
committerKIRAN KASHALKAR <kiran.kashalkar@riftio.com>
Thu, 20 Apr 2017 13:18:13 +0000 (09:18 -0400)
Signed-off-by: KIRAN KASHALKAR <kiran.kashalkar@riftio.com>
118 files changed:
Dockerfile [new file with mode: 0644]
Jenkinsfile [new file with mode: 0644]
skyquake/framework/utils/rw.js
skyquake/framework/utils/utils.js
skyquake/framework/widgets/button/rw.button.js
skyquake/framework/widgets/header/header.jsx
skyquake/framework/widgets/header/headerStore.js
skyquake/framework/widgets/listy/listy.js
skyquake/framework/widgets/login/login.jsx
skyquake/framework/widgets/skyquake_container/eventCenter.jsx
skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx
skyquake/framework/widgets/skyquake_container/skyquakeContainerSource.js
skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js
skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx
skyquake/framework/widgets/topology/topologyTree.jsx
skyquake/plugins/about/package.json
skyquake/plugins/about/scripts/build.sh
skyquake/plugins/about/src/about.jsx
skyquake/plugins/about/src/aboutStore.js
skyquake/plugins/about/webpack.production.config.js
skyquake/plugins/accounts/package.json
skyquake/plugins/accounts/scripts/build.sh
skyquake/plugins/accounts/src/account/account.jsx
skyquake/plugins/accounts/src/account/accountSource.js
skyquake/plugins/accounts/webpack.production.config.js
skyquake/plugins/composer/api/composer.js
skyquake/plugins/composer/package.json
skyquake/plugins/composer/routes.js
skyquake/plugins/composer/scripts/build.sh
skyquake/plugins/composer/src/src/actions/CatalogPackageManagerActions.js
skyquake/plugins/composer/src/src/components/CanvasPanel.js
skyquake/plugins/composer/src/src/components/CatalogPackageManager.js
skyquake/plugins/composer/src/src/components/CatalogPanel.js
skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js
skyquake/plugins/composer/src/src/components/DetailsPanel.js
skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js
skyquake/plugins/composer/src/src/components/filemanager/FileManager.jsx
skyquake/plugins/composer/src/src/components/filemanager/FileManagerSource.js
skyquake/plugins/composer/src/src/libraries/DeletionManager.js
skyquake/plugins/composer/src/src/libraries/SelectionManager.js
skyquake/plugins/composer/src/src/libraries/graph/DescriptorGraph.js
skyquake/plugins/composer/src/src/libraries/graph/HighlightRecordServicePaths.js
skyquake/plugins/composer/src/src/libraries/graph/layouts/RelationsAndNetworksLayout.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorModel.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFactory.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaFactory.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaProperty.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorModelSerializer.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorTemplateFactory.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorTemplates.js
skyquake/plugins/composer/src/src/libraries/model/descriptors/ForwardingGraph.js
skyquake/plugins/composer/src/src/libraries/model/descriptors/NetworkService.js
skyquake/plugins/composer/src/src/libraries/model/descriptors/RecordServicePath.js
skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunction.js
skyquake/plugins/composer/src/src/libraries/utils.js
skyquake/plugins/composer/src/src/sources/CatalogDataSource.js
skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js
skyquake/plugins/composer/src/src/stores/CatalogDataStore.js
skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js
skyquake/plugins/composer/src/src/stores/ComposerAppStore.js
skyquake/plugins/composer/test/spec/libraries/DescriptorModelFactorySpec.js
skyquake/plugins/composer/test/spec/libraries/DescriptorModelSpec.js
skyquake/plugins/composer/webpack.production.config.js
skyquake/plugins/config/package.json
skyquake/plugins/config/scripts/build.sh
skyquake/plugins/config/src/dashboard/inputs.jsx
skyquake/plugins/config/webpack.production.config.js
skyquake/plugins/debug/package.json
skyquake/plugins/debug/scripts/build.sh
skyquake/plugins/debug/src/crash.jsx
skyquake/plugins/debug/src/crashStore.js
skyquake/plugins/debug/webpack.production.config.js
skyquake/plugins/goodbyeworld/package.json
skyquake/plugins/goodbyeworld/scripts/build.sh
skyquake/plugins/goodbyeworld/webpack.production.config.js
skyquake/plugins/helloworld/package.json
skyquake/plugins/helloworld/scripts/build.sh
skyquake/plugins/helloworld/webpack.production.config.js
skyquake/plugins/launchpad/api/launchpad.js
skyquake/plugins/launchpad/package.json
skyquake/plugins/launchpad/scripts/build.sh
skyquake/plugins/launchpad/src/createStore.js
skyquake/plugins/launchpad/src/instantiate/instantiateDashboard.jsx
skyquake/plugins/launchpad/src/instantiate/instantiateStore.js
skyquake/plugins/launchpad/src/launchpad.jsx
skyquake/plugins/launchpad/src/launchpad.scss
skyquake/plugins/launchpad/src/launchpadFleetSource.js
skyquake/plugins/launchpad/src/launchpadFleetStore.js
skyquake/plugins/launchpad/src/launchpad_card/jobListCard.jsx
skyquake/plugins/launchpad/src/launchpad_card/jobListCard.scss
skyquake/plugins/launchpad/src/launchpad_card/nsrConfigPrimitives.jsx
skyquake/plugins/launchpad/src/launchpad_card/nsrScalingGroups.jsx
skyquake/plugins/launchpad/src/recordViewer/recordCard.jsx
skyquake/plugins/launchpad/src/recordViewer/recordViewSource.js
skyquake/plugins/launchpad/src/recordViewer/recordViewStore.js
skyquake/plugins/launchpad/src/ssh_keys/sshKeyStore.js
skyquake/plugins/launchpad/src/ssh_keys/sshKeys.jsx
skyquake/plugins/launchpad/src/topologyL2View/detailView.jsx
skyquake/plugins/launchpad/src/topologyL2View/topologyL2Source.js
skyquake/plugins/launchpad/src/topologyL2View/topologyL2Store.js
skyquake/plugins/launchpad/src/topologyView/topologySource.js
skyquake/plugins/launchpad/src/topologyView/topologyStore.js
skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkCreate.jsx
skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkCreateStore.js
skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkDetails.jsx
skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinks.jsx
skyquake/plugins/launchpad/src/vnfr/vnfrSource.js
skyquake/plugins/launchpad/src/vnfr/vnfrStore.js
skyquake/plugins/launchpad/webpack.production.config.js
skyquake/plugins/logging/package.json
skyquake/plugins/logging/scripts/build.sh
skyquake/plugins/logging/src/loggingGeneral.jsx
skyquake/plugins/logging/src/loggingStore.js
skyquake/plugins/logging/webpack.production.config.js
skyquake/scripts/build.sh
skyquake/skyquake.js
skyquake/tests/stories/catalogCard.js
skyquake/tests/stories/sshKeyCard.js

diff --git a/Dockerfile b/Dockerfile
new file mode 100644 (file)
index 0000000..40dc922
--- /dev/null
@@ -0,0 +1,24 @@
+FROM ubuntu:16.04
+
+RUN apt-get update && apt-get -y install python3 curl build-essential
+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/ && \
+       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
+
+RUN /usr/rift/container_tools/mkcontainer --modes build --modes ext --repo OSM
+
+RUN chmod 777 /usr/rift /usr/rift/usr/share
+
+RUN rm -rf /tmp/npm-cache
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644 (file)
index 0000000..dda86ae
--- /dev/null
@@ -0,0 +1,27 @@
+pipeline {
+       agent any
+       stages {
+               stage("Build") {
+                       agent {
+                               dockerfile true
+                       }
+                       steps {
+                               sh 'make NOT_DEVELOPER_BUILD=TRUE -j16 package'
+                               stash name: "deb-files", includes: ".build/*.deb"
+                       }
+               }
+               stage("Repo Component") {
+                       steps {
+                               unstash "deb-files"
+                               sh '''
+                                       mkdir -p pool/UI
+                                       mv .build/*.deb pool/UI/
+                                       mkdir -p dists/$RELEASE/UI/binary-amd64
+                                       apt-ftparchive packages pool/UI > dists/$RELEASE/UI/binary-amd64/Packages
+                                       gzip -9fk dists/$RELEASE/UI/binary-amd64/Packages
+                                       '''
+                               archiveArtifacts artifacts: "dists/**,pool/UI/*.deb"
+                       }
+               }
+       }
+}
index 1cca8f2..0c280c1 100644 (file)
  *     ['c', 'd']
  */
 
+import _isNumber from 'lodash/isNumber';
+import _each from 'lodash/each';
+import _flatten from 'lodash/flatten';
+import _union from 'lodash/union';
+
 var rw = rw || {
   // Angular specific for now but can be modified in one location if need ever be
   BaseController: function() {
@@ -304,7 +309,7 @@ rw.math = {
   },
 
   sum : function(total, i, key, value) {
-    if (_.isNumber(value)) {
+    if (_isNumber(value)) {
       total[key] = (i === 0 ? value : (total[key] + value));
     }
   },
@@ -312,7 +317,7 @@ rw.math = {
   sum2 : function(key) {
     return function(prev, cur, i) {
       var value = cur[key];
-      if (_.isNumber(value)) {
+      if (_isNumber(value)) {
         if (typeof(prev) === 'undefined') {
           return value;
         } else {
@@ -326,7 +331,7 @@ rw.math = {
   max : function(key) {
     return function(prev, cur, i) {
       var value = cur[key];
-      if (_.isNumber(value)) {
+      if (_isNumber(value)) {
         if (typeof(prev) === 'undefined') {
           return value;
         } else if (prev < value) {
@@ -370,7 +375,7 @@ rw.math = {
       operation(total, i, key, value);
     };
     for (i = 0; i < rows.length; i++) {
-      _.each(rows[i], f);
+      _each(rows[i], f);
     }
   }
 };
@@ -702,10 +707,10 @@ rw.api.setOffline(rw.search_params['offline']);
 
 rw.vnf = {
   ports: function(service) {
-    return _.flatten(jsonPath.eval(service, '$.connector[*].interface[*].port'));
+    return _flatten(jsonPath.eval(service, '$.connector[*].interface[*].port'));
   },
   fabricPorts: function(service) {
-    return _.flatten(jsonPath.eval(service, '$.vm[*].fabric.port'));
+    return _flatten(jsonPath.eval(service, '$.vm[*].fabric.port'));
   }
 };
 
@@ -749,7 +754,7 @@ rw.VcsVisitor.prototype = {
     }
     var i = 0;
     var self = this;
-    _.each(children, function(child) {
+    _each(children, function(child) {
       self.visit.call(self, child, parent, i, listType);
       i += 1;
     });
@@ -759,7 +764,7 @@ rw.VcsVisitor.prototype = {
 rw.vcs = {
 
   allVms : function() {
-    return _.flatten([this.jpath('$.collection[*].vm'), this.jpath('$.collection[*].collection[*].vm')], true);
+    return _flatten([this.jpath('$.collection[*].vm'), this.jpath('$.collection[*].collection[*].vm')], true);
   },
 
   vms: function(n) {
@@ -826,7 +831,7 @@ rw.vcs = {
   getChildren: function(n) {
     switch (rw.vcs.nodeType(n)) {
       case 'rwcolony':
-        return 'vm' in n ? _.union(n.collection, n.vm) : n.collection;
+        return 'vm' in n ? _union(n.collection, n.vm) : n.collection;
       case 'rwcluster':
         return n.vm;
       case 'RWVM':
@@ -838,7 +843,7 @@ rw.vcs = {
   },
 
   jpath : function(jpath, n) {
-    return _.flatten(jsonPath.eval(n || this, jpath), true);
+    return _flatten(jsonPath.eval(n || this, jpath), true);
   }
 };
 
index 16037d7..ed1f113 100644 (file)
@@ -18,9 +18,9 @@
 //Login needs to be refactored. Too many cross dependencies
 var AuthActions = require('../widgets/login/loginAuthActions.js');
 var $ = require('jquery');
-var rw = require('utils/rw.js');
+import rw from './rw.js';
 var API_SERVER = rw.getSearchParams(window.location).api_server;
-let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
+let NODE_PORT = rw.getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
 var SockJS = require('sockjs-client');
 
 var Utils = {};
index 41730eb..5d684ec 100644 (file)
@@ -251,7 +251,8 @@ module.exports = React.createClass({
         onKeyPress:        this.onKeyPress,
         onKeyUp:           this.onKeyUp,
         onFocus:           this.onFocus,
-        onBlur:            this.onBlur
+        onBlur:            this.onBlur,
+        disabled:          this.isDisabled, 
       },
       button_icon,
       React.createElement("span", {className: "rw-button__label"}, display)
index 21f4c10..3cfaaf6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -55,6 +55,7 @@ export default class AppHeader extends React.Component {
                 type={"error"}
                 hidden={!(this.state.validateErrorEvent && this.state.validateErrorMsg)}
                 onDismiss={HeaderStore.validateReset}
+                timeout= {5000}
             />
 
         // html = (
index 4ee86ff..150b325 100644 (file)
@@ -43,4 +43,4 @@ class HeaderStoreConstructor {
     }
 }
 
-export default Alt.createStore(HeaderStoreConstructor)
+export default Alt.createStore(HeaderStoreConstructor, 'HeaderStoreConstructor')
index ac0e58a..d032f97 100644 (file)
@@ -16,7 +16,9 @@
  *
  */
 import React from 'react';
-import _ from 'lodash';
+import _isArrayLike from 'lodash/isArrayLike';
+import _isObjectLike from 'lodash/isObjectLike';
+import _isEmpty from 'lodash/isEmpty';
 
 /**
  *
@@ -40,10 +42,10 @@ export default class Listy extends React.Component {
 
                var listNode = null;
                var self = this;
-               if (_.isArrayLike(data) && _.isObjectLike(data)) {
+               if (_isArrayLike(data) && _isObjectLike(data)) {
                        var children = [];
                        data.forEach(function(element, index, array) {
-                               if ( _.isArrayLike(element) || _.isObjectLike(element)) {
+                               if ( _isArrayLike(element) || _isObjectLike(element)) {
                                        children.push(self.createList(element, iter+1));
                                } else {
                                        children.push(React.createElement(itemTag, {
@@ -57,10 +59,10 @@ export default class Listy extends React.Component {
                                key: iter,
                                className: groupClass }, children);
                }
-               else if (_.isObjectLike(data)) {
+               else if (_isObjectLike(data)) {
                        var children = [];
                        for (var key in data) {
-                               if ( _.isArrayLike(data[key]) || _.isObjectLike(data[key])) {
+                               if ( _isArrayLike(data[key]) || _isObjectLike(data[key])) {
                                        children.push(
                                                React.createElement(listHeaderTag, {
                                                        key: key + '_header',
@@ -103,7 +105,7 @@ export default class Listy extends React.Component {
 
                return React.createElement("div", {
                        className: "listy" },
-                       _.isEmpty(data) ? 
+                       _isEmpty(data) ? 
                        this.noDataMessage() : 
                        this.createList(data)
                )
@@ -116,7 +118,7 @@ Listy.validateTagDefinition = function(props, propName, componentName) {
 
        if (!obj)
                return new Error('Validation failed. "%" is undefined', fullAttr);
-       if (!obj.hasOwnProperty("tag") || _.isEmpty(obj.tag))
+       if (!obj.hasOwnProperty("tag") || _isEmpty(obj.tag))
                return new Error('Validation failed. "%s" missing attribute "tag"', fullAttr);
        if (!obj.hasOwnProperty("className") || obj.className == undefined)
                return new Error('Validation failed. "%s" missing attribute "className"', fullAttr);
index 1506809..840a2bd 100644 (file)
@@ -19,7 +19,8 @@ import React from 'react';
 import Utils from 'utils/utils.js';
 import Button from 'widgets/button/rw.button.js';
 import './login.scss'
-let rw = require('utils/rw.js');
+import rw from 'utils/rw.js';
+
 class LoginScreen extends React.Component{
   constructor(props) {
     super(props);
index 75d2d52..7df4e3e 100644 (file)
@@ -28,7 +28,9 @@ import { Link } from 'react-router';
 import Utils from 'utils/utils.js';
 import Crouton from 'react-crouton';
 import TreeView from 'react-treeview';
-import _ from 'lodash';
+import _isEqual from 'lodash/isEqual';
+import _merge from 'lodash/merge';
+import _indexOf from 'lodash/indexOf';
 import '../../../node_modules/react-treeview/react-treeview.css';
 import './eventCenter.scss';
 
@@ -48,7 +50,7 @@ export default class EventCenter extends React.Component {
                if (props.newNotificationEvent && props.newNotificationMsg) {
                        if (latestNotification) {
                                latestNotification = JSON.parse(latestNotification);
-                               if (!_.isEqual(props.newNotificationMsg, latestNotification)) {
+                               if (!_isEqual(props.newNotificationMsg, latestNotification)) {
                                        stateObject.newNotificationEvent = props.newNotificationEvent;
                                        stateObject.newNotificationMsg = props.newNotificationMsg;
                                        sessionStorage.setItem('latestNotification', JSON.stringify(stateObject.newNotificationMsg));
@@ -67,7 +69,7 @@ export default class EventCenter extends React.Component {
                }
 
                if (notificationList) {
-                       stateObject.notifications = _.merge(notificationList, props.notifications);
+                       stateObject.notifications = _merge(notificationList, props.notifications);
                } else {
                        stateObject.notifications = props.notifications;
                }
@@ -112,7 +114,7 @@ export default class EventCenter extends React.Component {
                        notificationFields.eventTime = notification.eventTime;
 
                        Object.keys(notification).map((notificationKey) => {
-                               if (_.indexOf(['source', 'eventTime'], notificationKey) == -1) {
+                               if (_indexOf(['source', 'eventTime'], notificationKey) == -1) {
                                        notificationFields.eventKey = notificationKey;
                                        notificationFields.details = notification[notificationFields.eventKey];
                                }
index 6347c8f..8b61f4b 100644 (file)
@@ -24,7 +24,6 @@ import SkyquakeContainerActions from './skyquakeContainerActions.js'
 import SkyquakeContainerStore from './skyquakeContainerStore.js';
 // import Breadcrumbs from 'react-breadcrumbs';
 import Utils from 'utils/utils.js';
-import _ from 'lodash';
 import Crouton from 'react-crouton';
 import ScreenLoader from 'widgets/screen-loader/screenLoader.jsx';
 import './skyquakeApp.scss';
@@ -115,6 +114,7 @@ export default class skyquakeContainer extends React.Component {
                             type={notificationType}
                             hidden={!(displayNotification && notificationMessage)}
                             onDismiss={SkyquakeContainerActions.hideNotification}
+                            timeout= {5000}
                         />
                         <ScreenLoader show={displayScreenLoader}/>
                         <SkyquakeNav nav={nav}
index a26de2b..8afaf83 100644 (file)
 import Alt from './skyquakeAltInstance.js';
 import $ from 'jquery';
 import SkyquakeContainerActions from './skyquakeContainerActions'
+import rw from 'utils/rw.js';
+import Utils from 'utils/utils.js';
 
-let Utils = require('utils/utils.js');
-let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
+let API_SERVER = rw.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;
-let RW_REST_API_PORT = require('utils/rw.js').getSearchParams(window.location).rw_rest_api_port || 8008;
+let NODE_PORT = rw.getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
+let DEV_MODE = rw.getSearchParams(window.location).dev_mode || false;
+let RW_REST_API_PORT = rw.getSearchParams(window.location).rw_rest_api_port || 8008;
 
 if (DEV_MODE) {
     HOST = window.location.protocol + '//' + window.location.hostname;
@@ -78,7 +79,7 @@ export default {
             remote: function(state, location, streamSource) {
                 return new Promise((resolve, reject) => {
                     $.ajax({
-                        url: '//' + window.location.hostname + ':' + window.location.port + '/socket-polling',
+                        url: '//' + window.location.hostname + ':' + window.location.port + '/socket-polling?api_server=' + API_SERVER,
                         type: 'POST',
                         beforeSend: Utils.addAuthorizationStub,
                         data: {
index 4f27094..87651e5 100644 (file)
@@ -21,9 +21,11 @@ import Alt from './skyquakeAltInstance.js';
 import SkyquakeContainerSource from './skyquakeContainerSource.js';
 import SkyquakeContainerActions from './skyquakeContainerActions';
 let Utils = require('utils/utils.js');
-import _ from 'lodash';
+import _indexOf from 'lodash/indexOf';
+import _isEqual from 'lodash/isEqual';
 //Temporary, until api server is on same port as webserver
-var rw = require('utils/rw.js');
+import rw from 'utils/rw.js';
+
 var API_SERVER = rw.getSearchParams(window.location).api_server;
 var UPLOAD_SERVER = rw.getSearchParams(window.location).upload_server;
 
@@ -103,7 +105,7 @@ class SkyquakeContainerStore {
                 } else {
                     // Temp to test before adding multi-sources
                     data.notification.source = streamSource;
-                    if (_.indexOf(self.notifications, data.notification) == -1) {
+                    if (_indexOf(self.notifications, data.notification) == -1) {
                         // newly appreared event.
                         // Add to the notifications list and setState
                         self.notifications.unshift(data.notification);
@@ -178,7 +180,7 @@ class SkyquakeContainerStore {
                 Utils.checkAuthentication(data.statusCode, function() {
                     self.closeSocket();
                 });
-                if (!_.isEqual(data.project, self.projects)) {
+                if (!_isEqual(data.project, self.projects)) {
                     let user = self.user;
                     user.projects = data.project;
                     self.setState({
@@ -277,4 +279,4 @@ function getCurrentPlugin() {
     }
 }
 
-export default Alt.createStore(SkyquakeContainerStore);
+export default Alt.createStore(SkyquakeContainerStore, 'SkyquakeContainerStore');
index f9161cc..d9dff0b 100644 (file)
@@ -28,7 +28,8 @@ 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
-var rw = require('utils/rw.js');
+import rw from 'utils/rw.js';
+
 var API_SERVER = rw.getSearchParams(window.location).api_server;
 var UPLOAD_SERVER = rw.getSearchParams(window.location).upload_server;
 
index 5e0d895..9c77c13 100644 (file)
@@ -20,7 +20,7 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 import d3 from 'd3';
 import DashboardCard from '../dashboard_card/dashboard_card.jsx';
-import _ from 'lodash';
+import _cloneDeep from 'lodash/cloneDeep';
 import $ from 'jquery';
 import './topologyTree.scss';
 
@@ -66,7 +66,7 @@ export default class TopologyTree extends React.Component {
             //this.props.selectNode(props.data);
         }
         if(this.svg) {
-          this.update(_.cloneDeep(props.data));
+          this.update(_cloneDeep(props.data));
           // this.selectedID = props.data.id;
         }
     }
index cb92cf9..46829d5 100644 (file)
@@ -37,6 +37,7 @@
     "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",
index 03db9bd..3fd9253 100755 (executable)
@@ -27,6 +27,6 @@ echo 'Fetching third-party node_modules for '$PLUGIN_NAME
 npm install
 echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
 echo 'Packaging '$PLUGIN_NAME' using webpack'
-ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+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'
index 6458764..1a43aa4 100644 (file)
@@ -62,6 +62,23 @@ class About extends React.Component {
     // If in the mission control, create an uptime table;
     var uptime = this.state.createTime && this.state.createTime;
 
+    var fossInfoComponent = (
+      <div className="table-container">
+        <h2> FOSS Info </h2>
+        <table>
+          <thead>
+            <tr>
+              <th>
+                <a target="_blank" href='https://open.riftio.com/open-source-software-usage/'>
+                  Click here for FOSS Info (requires Internet connection)
+                </a>
+              </th>
+            </tr>
+          </thead>
+        </table>
+      </div>
+    );
+
     var uptimeComponent = (
       <div className="table-container">
                   <h2> Uptime Info </h2>
@@ -103,6 +120,7 @@ class About extends React.Component {
     if (this.state != null) {
       var html = (
               <div className="table-container-wrapper">
+                {fossInfoComponent}
                 {uptimeComponent}
                 <div className="table-container">
                   <h2> Version Info </h2>
index 934522b..bc94127 100644 (file)
@@ -37,5 +37,5 @@ aboutStore.prototype.getCreateTimeSuccess = function(time) {
        console.log('uptime success', time)
 }
 
-module.exports = Alt.createStore(aboutStore);;
+module.exports = Alt.createStore(aboutStore, 'aboutStore');;
 
index 5be840c..4a2aa12 100644 (file)
@@ -15,7 +15,7 @@
  *   limitations under the License.
  *
  */
-var Webpack = require('webpack');
+var webpack = require('webpack');
 var path = require('path');
 var nodeModulesPath = path.resolve(__dirname, 'node_modules');
 var buildPath = path.resolve(__dirname, 'public', 'build');
@@ -23,6 +23,8 @@ 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 = {
@@ -65,10 +67,24 @@ var config = {
     },
     plugins: [
         new HtmlWebpackPlugin({
-            filename: '../index.html'
-            , templateContent: '<div id="app"></div>'
-        }),
-        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+            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;
index e126042..d605080 100644 (file)
@@ -38,6 +38,7 @@
     "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",
index 921cfeb..f8dcff1 100755 (executable)
@@ -27,6 +27,6 @@ echo 'Fetching third-party node_modules for '$PLUGIN_NAME
 npm install
 echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
 echo 'Packaging '$PLUGIN_NAME' using webpack'
-ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+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'
index 92c368a..1916ef3 100644 (file)
@@ -18,7 +18,7 @@
 
 import React from 'react';
 import Button from 'widgets/button/rw.button.js';
-import _ from 'lodash';
+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';
@@ -95,7 +95,7 @@ class Account extends React.Component {
             }
         }
 
-        let newAccount = _.cloneDeep(removeTrailingWhitespace(Account));
+        let newAccount = _cloneDeep(removeTrailingWhitespace(Account));
         delete newAccount.params;
         newAccount.nestedParams &&
             newAccount.nestedParams['container-name'] &&
@@ -107,8 +107,8 @@ class Account extends React.Component {
             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.");
+         function(error) {
+            self.props.flux.actions.global.showNotification(error);
             self.props.flux.actions.global.hideScreenLoader.defer();
          });
     }
@@ -170,7 +170,7 @@ class Account extends React.Component {
     }
     evaluateSubmit = (e) => {
         if (e.keyCode == 13) {
-            if (this.props.edit) {
+            if (this.props.params.name != 'create') {
                 this.update(e);
             } else {
                 this.create(e);
index 08fb9f3..45da0fb 100644 (file)
@@ -38,7 +38,7 @@ module.exports = function(Alt) {
                 return resolve(false);
               }
                $.ajax({
-                url: '/socket-polling',
+                url: '/socket-polling?api_server=' + API_SERVER,
                 type: 'POST',
                 beforeSend: Utils.addAuthorizationStub,
                 data: {
@@ -50,6 +50,7 @@ module.exports = function(Alt) {
               }).fail(function(xhr){
                 //Authentication and the handling of fail states should be wrapped up into a connection class.
                 Utils.checkAuthentication(xhr.status);
+                reject(xhr.responseText || 'An error occurred. Check your logs for more information');
               });;
             });
           },
@@ -133,7 +134,7 @@ module.exports = function(Alt) {
               }).fail(function(xhr){
                 //Authentication and the handling of fail states should be wrapped up into a connection class.
                 Utils.checkAuthentication(xhr.status);
-                reject();
+                reject(xhr.responseText || 'An error occurred. Check your logs for more information');
               });
 
             });
@@ -177,7 +178,7 @@ module.exports = function(Alt) {
               }).fail(function(xhr){
                 //Authentication and the handling of fail states should be wrapped up into a connection class.
                 Utils.checkAuthentication(xhr.status);
-                reject('error');
+                reject(xhr.responseText || 'An error occurred. Check your logs for more information');
               });
 
             });
@@ -204,7 +205,7 @@ module.exports = function(Alt) {
               }).fail(function(xhr){
                 //Authentication and the handling of fail states should be wrapped up into a connection class.
                 Utils.checkAuthentication(xhr.status);
-                reject('error');
+                reject(xhr.responseText || 'An error occurred. Check your logs for more information');
               });
             })
           },
index 3984a58..6356ddb 100644 (file)
@@ -15,7 +15,7 @@
  *   limitations under the License.
  *
  */
-var Webpack = require('webpack');
+var webpack = require('webpack');
 var path = require('path');
 var nodeModulesPath = path.resolve(__dirname, 'node_modules');
 var buildPath = path.resolve(__dirname, 'public', 'build');
@@ -23,7 +23,7 @@ var mainPath = path.resolve(__dirname, 'src', 'main.js');
 var uiPluginCmakeBuild = process.env.ui_plugin_cmake_build || false;
 var frameworkPath = uiPluginCmakeBuild?'../../../../skyquake/skyquake-build/framework':'../../framework';
 var HtmlWebpackPlugin = require('html-webpack-plugin');
-var CommonsPlugin = new require("webpack/lib/optimize/CommonsChunkPlugin")
+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 = {
@@ -66,10 +66,24 @@ var config = {
     },
     plugins: [
         new HtmlWebpackPlugin({
-            filename: '../index.html'
-            , templateContent: '<div id="app"></div>'
-        }),
-        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+            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;
index 61cf79b..f2409d6 100644 (file)
@@ -29,6 +29,7 @@ var PackageFileHandler = require('./packageFileHandler.js');
 
 var Composer = {};
 var FileManager = {};
+var PackageManager = {};
 var DataCenters = {};
 // Catalog module methods
 Composer.get = function(req) {
@@ -304,8 +305,8 @@ Composer.updateSave = function(req) {
     });
 }
 
-Composer.update = function(req) {
-    console.log(' Updating file', req.file.originalname, 'as', req.file.filename);
+PackageManager.upload = function(req) {
+    console.log(' Uploading file', req.file.originalname, 'as', req.file.filename);
     var api_server = req.query['api_server'];
     // dev_download_server is for testing purposes.
     // It is the direct IP address of the Node server where the
@@ -313,15 +314,16 @@ Composer.update = function(req) {
     var download_host = req.query['dev_download_server'];
 
     if (!download_host) {
-        download_host = req.protocol + '://' + req.get('host');//api_server + ':' + utils.getPortForProtocol(req.protocol);
+        download_host = req.protocol + '://' + req.get('host');//req.api_server + ':' + utils.getPortForProtocol(req.protocol);
     }
+
     var input = {
-        'external-url': download_host + '/composer/update/' + req.file.filename,
+        'external-url': download_host + '/composer/upload/' + req.file.filename,
         'package-type': 'VNFD',
         'package-id': uuid()
     }
 
-    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-update');
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-create');
 
     input = utils.addProjectContextToRPCPayload(req, uri, input);
 
@@ -355,7 +357,7 @@ Composer.update = function(req) {
             });
         }).catch(function(error) {
             var res = {};
-            console.log('Problem with Composer.upload', error);
+            console.log('Problem with PackageManager.upload', error);
             res.statusCode = error.statusCode || 500;
             res.errorMessage = {
                 error: 'Failed to upload package ' + req.file.originalname + '. Error: ' + error
@@ -365,8 +367,8 @@ Composer.update = function(req) {
     });
 };
 
-Composer.upload = function(req) {
-    console.log(' Uploading file', req.file.originalname, 'as', req.file.filename);
+PackageManager.update = function(req) {
+    console.log(' Updating file', req.file.originalname, 'as', req.file.filename);
     var api_server = req.query['api_server'];
     // dev_download_server is for testing purposes.
     // It is the direct IP address of the Node server where the
@@ -374,16 +376,15 @@ Composer.upload = function(req) {
     var download_host = req.query['dev_download_server'];
 
     if (!download_host) {
-        download_host = req.protocol + '://' + req.get('host');//req.api_server + ':' + utils.getPortForProtocol(req.protocol);
+        download_host = req.protocol + '://' + req.get('host');//api_server + ':' + utils.getPortForProtocol(req.protocol);
     }
-
     var input = {
-        'external-url': download_host + '/composer/upload/' + req.file.filename,
+        'external-url': download_host + '/composer/update/' + req.file.filename,
         'package-type': 'VNFD',
         'package-id': uuid()
     };
 
-    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-create');
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-update');
 
     input = utils.addProjectContextToRPCPayload(req, uri, input);
 
@@ -408,7 +409,7 @@ Composer.upload = function(req) {
             data['transaction_id'] = result[0].body['output']['transaction-id'];
 
             // Add a status checker on the transaction and then to delete the file later
-            PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id']);
+            PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id'], true);
 
             // Return status to composer UI to update the status.
             resolve({
@@ -427,33 +428,16 @@ Composer.upload = function(req) {
     });
 };
 
-
-
-Composer.addFile = function(req) {
-    console.log(' Uploading file', req.file.originalname, 'as', req.file.filename);
+PackageManager.export = function(req) {
+    // /api/operations/package-export
     var api_server = req.query['api_server'];
-    var download_host = req.query['dev_download_server'];
-    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) {
-        download_host = req.protocol + '://' + req.get('host');//api_server + ':' + utils.getPortForProtocol(req.protocol);
-    }
-    var input = {
-        'external-url': download_host + '/composer/upload/' + req.query['package_id'] + '/' + req.file.filename,
-        'package-type': package_type,
-        'package-id': package_id,
-        'package-path': package_path + '/' + req.file.filename
-    }
-
-    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-file-add');
-
+    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: uri,
+                uri: utils.confdPort(api_server) + '/api/operations/package-export',
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
                     'Authorization': req.session && req.session.authorization
@@ -462,47 +446,46 @@ Composer.addFile = function(req) {
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true,
                 json: true,
-                body: {
-                    input: input
-                }
+                body: { "input": input }
             })
         ]).then(function(result) {
             var data = {};
-            data['transaction_id'] = result[0].body['output']['task-id'];
             resolve({
                 statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
-                data: data
+                data: result[0].body
             });
         }).catch(function(error) {
             var res = {};
-            console.log('Problem with Composer.upload', error);
+            console.log('Problem with PackageManager.export', error);
             res.statusCode = error.statusCode || 500;
             res.errorMessage = {
-                error: 'Failed to upload package ' + req.file.originalname + '. Error: ' + error
+                error: error
             };
             reject(res);
         });
     });
 }
 
-Composer.exportPackage = function(req) {
+PackageManager.copy = function(req) {
+    // /api/operations/package-copy
     var api_server = req.query['api_server'];
-    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-export');
+    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: uri,
                 method: 'POST',
                 headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.session && req.session.authorization
+                    'Authorization': req.get('Authorization')
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true,
                 json: true,
-                body: { "input": input }
+                body: { "input": input}
             })
         ]).then(function(result) {
             var data = {};
@@ -512,7 +495,7 @@ Composer.exportPackage = function(req) {
             });
         }).catch(function(error) {
             var res = {};
-            console.log('Problem with Composer.exportPackage', error);
+            console.log('Problem with PackageManager.copy', error);
             res.statusCode = error.statusCode || 500;
             res.errorMessage = {
                 error: error
@@ -522,6 +505,100 @@ Composer.exportPackage = function(req) {
     });
 }
 
+/**
+ * This 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) {
+    var api_server = req.query["api_server"];
+    var uri = utils.confdPort(api_server);
+    var id = req.params['id'];
+    var url = utils.projectContextUrl(req, uri + '/api/operational/copy-jobs' + (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')
+            }),
+            forever: constants.FOREVER_ON,
+            rejectUnauthorized: false
+        }, function(error, response, body) {
+            if (utils.validateResponse('restconfAPI.streams', error, response, body, resolve, reject)) {
+                var returnData;
+                if (id) {
+                    returnData = JSON.parse(response.body)['rw-pkg-mgmt:job'];
+                } else {
+                    var data = JSON.parse(response.body)['rw-pkg-mgmt:copy-jobs'];
+                    returnData = (data && data.job) || [];
+                }
+                resolve({
+                    statusCode: response.statusCode,
+                    data: returnData
+                })
+            };
+        })
+    })
+}
+
+FileManager.addFile = function(req) {
+    console.log(' Uploading file', req.file.originalname, 'as', req.file.filename);
+    var api_server = req.query['api_server'];
+    var download_host = req.query['dev_download_server'];
+    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) {
+        download_host = req.protocol + '://' + req.get('host');//api_server + ':' + utils.getPortForProtocol(req.protocol);
+    }
+    var input = {
+        'external-url': download_host + '/composer/upload/' + req.query['package_id'] + '/' + req.file.filename,
+        'package-type': package_type,
+        'package-id': package_id,
+        'package-path': package_path + '/' + req.file.filename
+    };
+
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-file-add');
+
+    input = utils.addProjectContextToRPCPayload(req, uri, input);
+
+
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            rp({
+                uri: uri,
+                method: 'POST',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
+                    'Authorization': req.get('Authorization')
+                }),
+                forever: constants.FOREVER_ON,
+                rejectUnauthorized: false,
+                resolveWithFullResponse: true,
+                json: true,
+                body: {
+                    input: input
+                }
+            })
+        ]).then(function(result) {
+            var data = {};
+            data['transaction_id'] = result[0].body['output']['task-id'];
+            resolve({
+                statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+                data: data
+            });
+        }).catch(function(error) {
+            var res = {};
+            console.log('Problem with Composer.upload', error);
+            res.statusCode = error.statusCode || 500;
+            res.errorMessage = {
+                error: 'Failed to upload package ' + req.file.originalname + '. Error: ' + error
+            };
+            reject(res);
+        });
+    });
+}
+
 FileManager.get = function(req) {
     var api_server = req.query['api_server'];
     var type = req.query['package_type'] && req.query['package_type'].toUpperCase();
@@ -679,5 +756,6 @@ FileManager.job = function(req) {
 }
 module.exports = {
     Composer:Composer,
-    FileManager: FileManager
+    FileManager: FileManager,
+    PackageManager: PackageManager
 };
index 6a01af1..5b45239 100644 (file)
@@ -53,6 +53,7 @@
     "babel-preset-react": "^6.5.0",
     "babel-preset-stage-0": "^6.3.13",
     "babel-runtime": "^6.3.19",
+    "compression-webpack-plugin": "^0.3.2",
     "css-loader": "^0.23.0",
     "eslint": "^1.10.2",
     "eslint-loader": "^1.1.1",
index 3782209..b3641aa 100644 (file)
@@ -23,6 +23,7 @@ var constants = require('../../framework/core/api_utils/constants.js');
 var C = require('./api/composer.js');
 var Composer = C.Composer;
 var FileManager = C.FileManager;
+var PackageManager = C.PackageManager;
 var multer = require('multer');
 var fs = require('fs');
 var path = require('path');
@@ -103,58 +104,73 @@ router.put('/api/catalog/:catalogType/:id', cors(), function(req, res) {
         res.send(error.errorMessage);
     });
 });
-router.post('/upload', cors(), upload.single('package'), function (req, res, next) {
-    Composer.upload(req).then(function(data) {
+
+router.post('/api/file-manager', cors(), upload.single('package'), 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.post('/update', cors(), upload.single('package'), function (req, res, next) {
-    Composer.update(req).then(function(data) {
+router.get('/api/file-manager', cors(), function(req, res) {
+    FileManager.get(req).then(function(data) {
+        utils.sendSuccessResponse(data, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+})
+router.get('/api/file-manager/jobs/:id', cors(), function(req, res) {
+    FileManager.job(req).then(function(data) {
+        utils.sendSuccessResponse(data, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+router.delete('/api/file-manager', cors(), function(req, res) {
+    FileManager.get(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
 });
-router.use('/update', cors(), express.static('upload/packages'));
-
 
+// Catalog operations via package manager
 
-router.post('/api/file-manager', cors(), upload.single('package'), function (req, res, next) {
-    Composer.addFile(req).then(function(data) {
+router.post('/upload', cors(), upload.single('package'), function (req, res, next) {
+    PackageManager.upload(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
 });
+router.use('/upload', cors(), express.static('upload/packages'));
 
-router.get('/api/file-manager', cors(), function(req, res) {
-    FileManager.get(req).then(function(data) {
+router.post('/update', cors(), upload.single('package'), function (req, res, next) {
+    PackageManager.update(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
-})
-router.get('/api/file-manager/jobs/:id', cors(), function(req, res) {
-    FileManager.job(req).then(function(data) {
+});
+router.use('/update', cors(), express.static('upload/packages'));
+
+router.post('/api/package-export', cors(), function (req, res, next) {
+    PackageManager.export(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
 });
-router.delete('/api/file-manager', cors(), function(req, res) {
-    FileManager.get(req).then(function(data) {
+router.post('/api/package-copy', cors(), function (req, res, next) {
+    PackageManager.copy(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
     });
 });
-
-router.post('/api/package-export', cors(), function (req, res, next) {
-    Composer.exportPackage(req).then(function(data) {
+router.get('/api/package-manager/jobs/:id', cors(), function (req, res, next) {
+    PackageManager.getJobStatus(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
index eb17e4e..ae23fac 100755 (executable)
@@ -27,6 +27,6 @@ echo 'Fetching third-party node_modules for '$PLUGIN_NAME
 npm install
 echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
 echo 'Packaging '$PLUGIN_NAME' using webpack'
-ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+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'
index 22e32d3..2769c33 100644 (file)
@@ -21,7 +21,17 @@ import alt from '../alt';
 class CatalogPackageManagerActions {
 
        constructor() {
-               this.generateActions('downloadCatalogPackage', 'downloadCatalogPackageStatusUpdated', 'downloadCatalogPackageError', 'uploadCatalogPackage', 'uploadCatalogPackageStatusUpdated', 'uploadCatalogPackageError', 'removeCatalogPackage');
+               this.generateActions(
+                       'downloadCatalogPackage', 
+                       'downloadCatalogPackageStatusUpdated',
+                       'downloadCatalogPackageError', 
+                       'uploadCatalogPackage', 
+                       'uploadCatalogPackageStatusUpdated', 
+                       'uploadCatalogPackageError',
+                       'copyCatalogPackage', 
+                       'updateStatus', 
+                       'removeCatalogOperation'
+                       );
        }
 
 }
index 9eece49..160db5f 100644 (file)
@@ -18,7 +18,7 @@
  */
 'use strict';
 
-import _ from 'lodash'
+import _includes from 'lodash/includes'
 import cc from 'change-case'
 import React from 'react'
 import PureRenderMixin from 'react-addons-pure-render-mixin'
@@ -115,7 +115,7 @@ const CanvasPanel = React.createClass({
                );
        },
        onDragOver(event) {
-               const isDraggingFiles = _.includes(event.dataTransfer.types, 'Files');
+               const isDraggingFiles = _includes(event.dataTransfer.types, 'Files');
                if (!isDraggingFiles) {
                        event.preventDefault();
                        event.dataTransfer.dropEffect = 'copy';
index c0b996c..0811093 100644 (file)
@@ -88,7 +88,7 @@ const CatalogPackageManager = React.createClass({
 
                var createItem = function (catalogPackage) {
                        const onClickRemove = function () {
-                               CatalogPackageManagerActions.removeCatalogPackage(catalogPackage);
+                               CatalogPackageManagerActions.removeCatalogOperation(catalogPackage);
                        };
                        const classNames = ClassNames('item', {'-error': catalogPackage.error, '-success': catalogPackage.success});
                        return (
@@ -106,11 +106,11 @@ const CatalogPackageManager = React.createClass({
                        );
                };
 
-               const packages = this.state.packages || [];
+               const operations = this.state.operations || [];
                return (
                        <div className="CatalogPackageManager">
                                <div className="items">
-                                       {packages.map(createItem)}
+                                       {operations.map(createItem)}
                                </div>
                        </div>
                );
index a3574e7..6d58c8a 100644 (file)
@@ -18,7 +18,7 @@
  */
 'use strict';
 
-import _ from 'lodash'
+import _includes from 'lodash/includes'
 import React from 'react'
 import ReactDOM from 'react-dom'
 import messages from './messages'
@@ -167,7 +167,7 @@ const CatalogPanel = React.createClass({
                        uiTransientState.isDrop = false;
                        uiTransientState.isDragging = true;
                        uiTransientState.wasTrayOpen = this.state.isTrayOpen;
-                       uiTransientState.isDraggingFiles = _.includes(e.dataTransfer.types, 'Files');
+                       uiTransientState.isDraggingFiles = _includes(e.dataTransfer.types, 'Files');
                        const dragState = ComposerAppStore.getState().drag || {};
                        if (uiTransientState.isDraggingFiles || (dragState.type === 'catalog-item')) {
                                CatalogPanelTrayActions.open();
index 896badd..bc8db58 100644 (file)
@@ -102,6 +102,7 @@ const CatalogHeader = React.createClass({
                CatalogItemsActions.createCatalogItem(type);
        },
        onClickDuplicateCatalogItem() {
+               CatalogPanelTrayActions.open();
                CatalogItemsActions.duplicateSelectedCatalogItem();
        },
        onClickExportCatalogItems() {
index 37fc17b..e13282e 100644 (file)
@@ -18,7 +18,7 @@
  */
 'use strict';
 
-import _ from 'lodash'
+import _cloneDeep from 'lodash/cloneDeep'
 import React from 'react';
 import PureRenderMixin from 'react-addons-pure-render-mixin'
 import messages from './messages'
@@ -61,7 +61,7 @@ const DetailsPanel = React.createClass({
                const selectedContainer = selected[0];
                if (selectedContainer) {
                        bodyComponent = <CatalogItemDetailsEditor container={selectedContainer} width={this.props.layout.right} />;
-                       const edit = _.cloneDeep(selectedContainer.model);
+                       const edit = _cloneDeep(selectedContainer.model);
                        json = serializers.serialize(edit) || edit;
                }
                const jsonViewerTitle = selectedContainer ? selectedContainer.model.name : 'nothing selected';
index 65712f4..22ba179 100644 (file)
  *
  * This class generates the form fields used to edit the CONFD JSON model.
  */
-'use strict';
 
-import _ from 'lodash'
+import _includes from 'lodash/includes'
+import _isArray from 'lodash/isArray'
+import _cloneDeep from 'lodash/cloneDeep'
+import _debounce from 'lodash/debounce';
 import utils from '../libraries/utils'
 import React from 'react'
 import ClassNames from 'classnames'
@@ -46,13 +48,15 @@ import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg'
 
 import '../styles/EditDescriptorModelProperties.scss'
 
+const EMPTY_LEAF_PRESENT = '--empty-leaf-set--';
+
 function getDescriptorMetaBasicForType(type) {
-       const basicPropertiesFilter = d => _.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
+       const basicPropertiesFilter = d => _includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
        return DescriptorModelMetaFactory.getModelMetaForType(type, basicPropertiesFilter) || {properties: []};
 }
 
 function getDescriptorMetaAdvancedForType(type) {
-       const advPropertiesFilter = d => !_.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
+       const advPropertiesFilter = d => !_includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
        return DescriptorModelMetaFactory.getModelMetaForType(type, advPropertiesFilter) || {properties: []};
 }
 
@@ -138,7 +142,10 @@ export default function EditDescriptorModelProperties(props) {
                                create(model, path, property);
                        } else {
                                const name = path.join('.');
-                               const value = Property.createModelInstance(property);
+                               // 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());
@@ -171,30 +178,43 @@ export default function EditDescriptorModelProperties(props) {
                );
        }
 
-       function onFormFieldValueChanged(event) {
-               if (DescriptorModelFactory.isContainer(this)) {
-                       event.preventDefault();
-                       const name = event.target.name;
-                       const value = event.target.value;
-                       utils.assignPathValue(this.model, name, value);
-                       CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
-               }
-       }
-
        function buildField(container, property, path, value, fieldKey) {
                let cds = CatalogDataStore;
                let catalogs = cds.getTransientCatalogs();
 
-               const name = path.join('.');
+               const pathToProperty = path.join('.');
+               const isEditable = true;
                const isGuid = Property.isGuid(property);
                const isBoolean = Property.isBoolean(property);
-               const onChange = onFormFieldValueChanged.bind(container);
                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 : '' : undefined;
+               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) => {
@@ -202,82 +222,165 @@ export default function EditDescriptorModelProperties(props) {
                                // 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={fieldKey.toString() + ':' + i} value={d.name}>{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)' + fieldKey.toString()} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
+                               options.unshift(<option key={'(value-not-in-enum)'} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
                        }
-                       return <select key={fieldKey.toString()} id={fieldKey.toString()} className={ClassNames({'-value-not-set': !isValueSet})} name={name} value={value} title={name} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} disabled={!isEditable}>{options}</select>;
+                       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} 
+                                       disabled={!isEditable}>
+                                               {options}
+                               </select>
+                       );
                }
 
                if (isLeafRef) {
-                       let fullFieldKey = _.isArray(fieldKey) ? fieldKey.join(':') : fieldKey;
+                       let fullPathString = container.key + ':' + path.join(':');
                        let containerRef = container;
                        while (containerRef.parent) {
-                               fullFieldKey = containerRef.parent.key + ':' + fullFieldKey;
+                               fullPathString = containerRef.parent.key + ':' + fullPathString;
                                containerRef = containerRef.parent;
                        }
-                       const leafRefPathValues = Property.getLeafRef(property, path, value, fullFieldKey, catalogs, container);
+                       const leafRefPathValues = Property.getLeafRef(property, path, value, fullPathString, catalogs, container);
 
                        const options = leafRefPathValues && leafRefPathValues.map((d, i) => {
-                               return <option key={fieldKey.toString() + ':' + i} value={d.value}>{d.value}</option>;
+                               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)' + fieldKey.toString()} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
+                               options.unshift(<option key={'(value-not-in-leafref)'} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
                        }
-                       return <select key={fieldKey.toString()} id={fieldKey.toString()} className={ClassNames({'-value-not-set': !isValueSet})} name={name} value={value} title={name} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} disabled={!isEditable}>{options}</select>;
+                       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} 
+                                       disabled={!isEditable}>
+                                               {options}
+                               </select>
+                       );
                }
 
                if (isBoolean) {
-                       let fullFieldKey = _.isArray(fieldKey) ? fieldKey.join(':') : fieldKey;
-                       let containerRef = container;
-                       while (containerRef.parent) {
-                               fullFieldKey = containerRef.parent.key + ':' + fullFieldKey;
-                               containerRef = containerRef.parent;
-                       }
-
                        const options = [
-                               <option key={fieldKey.toString() + '-true'} value="TRUE">TRUE</option>,
-                               <option key={fieldKey.toString() + '-false'} value="FALSE">FALSE</option>
+                               <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)' + fieldKey.toString()} value="" placeholder={placeholder}></option>);
+                               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.toString()} id={fieldKey.toString()} className={ClassNames({'-value-not-set': !isValueSet})} name={name} value={val && val.toUpperCase()} title={name} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} disabled={!isEditable}>{options}</select>;
+                       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} 
+                                       disabled={!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} 
+                                       disabled={!isEditable}>
+                                               {options}
+                               </select>
+                       );
                }
 
                if (property['preserve-line-breaks']) {
-                       return <textarea key={fieldKey.toString()} cols="5" id={fieldKey.toString()} name={name} value={value} placeholder={placeholder} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} onMouseOut={endEditing} onMouseLeave={endEditing} readOnly={!!isEditable} />;
+                       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} 
+                                       disabled={!isEditable} />
+                       );
                }
 
-               return <input
-                                       key={fieldKey.toString()}
-                                       id={fieldKey.toString()}
-                                       type="text"
-                                       name={name}
-                                       value={fieldValue}
-                                       className={className}
-                                       placeholder={placeholder}
-                                       onChange={onChange}
-                                       onFocus={onFocus}
-                                       onBlur={endEditing}
-                                       onMouseDown={startEditing}
-                                       onMouseOver={startEditing}
-                                       onMouseOut={endEditing}
-                                       onMouseLeave={endEditing}
-                                       readOnly={!isEditable}
-               />;
+               return (
+                       <input 
+                               key={fieldKey}
+                               id={fieldKey}
+                               type="text"
+                               defaultValue={fieldValue}
+                               className={className}
+                               placeholder={placeholder}
+                               onChange={onTextChange}
+                               onFocus={onFocus}
+                               onBlur={endEditing}
+                               onMouseDown={startEditing}
+                               onMouseOver={startEditing}
+                               onMouseOut={endEditing}
+                               onMouseLeave={endEditing}
+                               disabled={!isEditable}
+                       />
+               );
 
        }
 
@@ -298,15 +401,9 @@ export default function EditDescriptorModelProperties(props) {
 
        function buildChoice(container, property, path, value, key) {
 
-               function onFormFieldValueChanged(event) {
+               function processChoiceChange(name, value) {
                        if (DescriptorModelFactory.isContainer(this)) {
 
-                               event.preventDefault();
-
-                               let name = event.target.name;
-                               const value = event.target.value;
-
-
                                /*
                                        Transient State is stored for convenience in the uiState field.
                                        The choice yang type uses case elements to describe the "options".
@@ -343,7 +440,7 @@ export default function EditDescriptorModelProperties(props) {
                                        isTopCase = true;
                                        choiceObject = utils.resolvePath(this.model, [selected].join('.'));
                                }
-                               utils.assignPathValue(stateObject, [selected].join('.'), _.cloneDeep(choiceObject));
+                               utils.assignPathValue(stateObject, [selected].join('.'), _cloneDeep(choiceObject));
 
                                if(selected) {
                                        if(this.model.uiState.choice.hasOwnProperty(name)) {
@@ -365,7 +462,6 @@ export default function EditDescriptorModelProperties(props) {
                                        utils.assignPathValue(this.model, [value].join('.'), newChoiceObject)
                                }
 
-
                                // update the selected name
                                utils.assignPathValue(this.model, statePath.concat('selected').join('.'), value);
 
@@ -373,13 +469,20 @@ export default function EditDescriptorModelProperties(props) {
                        }
                }
 
+               const pathToChoice = path.join('.');
                const caseByNameMap = {};
 
-               const onChange = onFormFieldValueChanged.bind(container);
+               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') {
-                               caseByNameMap[d.name] = d.properties[0];
+                               //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,
@@ -400,39 +503,40 @@ export default function EditDescriptorModelProperties(props) {
                        );
                });
 
-               const selectName = path.join('.');
-               let selectedOptionPath = ['uiState.choice', selectName, 'selected'].join('.');
+               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, selectName);
+                       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(fieldProperties.hasOwnProperty(c.optionValue.split('.')[1])) {
-                                               utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), c.optionValue);
+                                               utils.assignPathValue(container.model, ['uiState.choice', pathToChoice, 'selected'].join('.'), c.optionValue);
                                        }
                                });
-                               selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.'));
+                               selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', pathToChoice, 'selected'].join('.'));
                        } else {
                                property.properties.map(function(p) {
                                        let pname = p.properties[0].name;
                                        if(container.model.hasOwnProperty(pname)) {
-                                               utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), [p.name, pname].join('.'));
+                                               utils.assignPathValue(container.model, ['uiState.choice', pathToChoice, 'selected'].join('.'), [p.name, pname].join('.'));
                                        }
                                })
-                               selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].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 hasProperties = _isArray(valueProperty.properties) && valueProperty.properties.length;
                const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(valueProperty);
                //Some magic that prevents errors for arising
-               const valueResponse = valueProperty.properties.length ? valueProperty.properties.map((d, i) => {
+               const valueResponse = valueProperty.properties && valueProperty.properties.length ? valueProperty.properties.map(valuePropertyFn) : (!isMissingDescriptorMeta) ? build(container, valueProperty, path.concat(valueProperty.name), utils.resolvePath(container.model, path.concat(valueProperty.name).join('.')) || container.model[valueProperty.name]) :
+               valueProperty.map && valueProperty.map(valuePropertyFn);
+               function valuePropertyFn(d, i) {
                        const childPath = path.concat(valueProperty.name, d.name);
                        const childValue = utils.resolvePath(container.model, childPath.join('.'));
                        return (
@@ -440,13 +544,25 @@ export default function EditDescriptorModelProperties(props) {
                                        {build(container, d, childPath, childValue, props)}
                                </div>
                        );
-               }) : (!isMissingDescriptorMeta) ? build(container, valueProperty, path.concat(valueProperty.name), utils.resolvePath(container.model, path.concat(valueProperty.name).join('.')) || container.model[valueProperty.name]) : null
+               }
                // end magic
                const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
 
                return (
                        <div key={key} className="choice">
-                               <select key={Date.now()} className={ClassNames({'-value-not-set': !selectedOptionValue})} name={selectName} value={selectedOptionValue} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} onMouseOut={endEditing} onMouseLeave={endEditing} readOnly={!isEditable}>
+                               <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}
+                                       disabled={!isEditable}
+                               >
                                        {options}
                                </select>
                                {valueResponse}
@@ -455,13 +571,13 @@ export default function EditDescriptorModelProperties(props) {
 
        }
 
-       function buildSimpleListItem(container, property, path, value, key, index) {
+       function buildSimpleListItem(container, property, path, value, uniqueId, index) {
                // todo need to abstract this better
                const title = getTitle(value);
                var req = require.context("../", true, /\.svg/);
                return (
-                       <div>
-                               <a href="#select-list-item" key={Date.now()} className={property.name + '-list-item simple-list-item '} onClick={onClickSelectItem.bind(container, property, path, value)}>
+                       <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>
@@ -470,10 +586,10 @@ export default function EditDescriptorModelProperties(props) {
                );
        }
 
-       function buildRemoveListItem(container, property, valuePath, fieldKey, index) {
+       function buildRemoveListItem(container, property, valuePath, index) {
                const className = ClassNames(property.name + '-remove actions');
                return (
-                       <div key={fieldKey.concat(index).join(':')} className={className}>
+                       <div className={className}>
                                <h3>
                                        <span className={property.type + '-name name'}>{changeCase.title(property.name)}</span>
                                        <span className="info">{index + 1}</span>
@@ -483,12 +599,12 @@ export default function EditDescriptorModelProperties(props) {
                );
        }
 
-       function buildLeafListItem(container, property, valuePath, value, key, index) {
+       function buildLeafListItem(container, property, valuePath, value, uniqueId, index) {
                // look at the type to determine how to parse the value
                return (
-                       <div>
-                               {buildRemoveListItem(container, property, valuePath, key, index)}
-                               {buildField(container, property, valuePath, value, key)}
+                       <div key={uniqueId}>
+                               {buildRemoveListItem(container, property, valuePath, index)}
+                               {buildField(container, property, valuePath, value, uniqueId)}
                        </div>
 
                );
@@ -501,19 +617,28 @@ export default function EditDescriptorModelProperties(props) {
                const isArray = Property.isArray(property);
                const isObject = Property.isObject(property);
                const isLeafList = Property.isLeafList(property);
-               const fieldKey = [container.id].concat(path);
                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(':')
+
                if (!property.properties && isObject) {
                        const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
                        property.properties = uiState.properties;
                }
 
-               const hasProperties = _.isArray(property.properties) && property.properties.length;
+               const hasProperties = _isArray(property.properties) && property.properties.length;
                const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(property);
 
                // ensure value is not undefined for non-leaf property types
@@ -522,7 +647,7 @@ export default function EditDescriptorModelProperties(props) {
                                value = isArray ? [] : {};
                        }
                }
-               const valueAsArray = _.isArray(value) ? value : isLeafList && typeof value === 'undefined' ? [] : [value];
+               const valueAsArray = _isArray(value) ? value : isLeafList && typeof value === 'undefined' ? [] : [value];
 
                const isMetaField = property.name === 'meta';
                const isCVNFD = property.name === 'constituent-vnfd';
@@ -531,12 +656,16 @@ export default function EditDescriptorModelProperties(props) {
                valueAsArray.forEach((value, index) => {
 
                        let field;
-                       const key = fieldKey.slice();
                        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);
-                               key.push(index);
+                               fieldId += index;
                        }
 
                        if (isMetaField) {
@@ -548,17 +677,17 @@ export default function EditDescriptorModelProperties(props) {
                        }
 
                        if (isMissingDescriptorMeta) {
-                               field = <span key={key.concat('warning').join(':')} className="warning">No Descriptor Meta for {property.name}</span>;
+                               field = <span key={fieldId} className="warning">No Descriptor Meta for {property.name}</span>;
                        } else if (property.type === 'choice') {
-                               field = buildChoice(container, property, valuePath, value, key.join(':'));
+                               field = buildChoice(container, property, valuePath, value, fieldId);
                        } else if (isSimpleListView) {
-                               field = buildSimpleListItem(container, property, valuePath, value, key, index);
+                               field = buildSimpleListItem(container, property, valuePath, value, fieldId, index);
                        } else if (isLeafList) {
-                               field = buildLeafListItem(container, property, valuePath, value, key, index);
+                               field = buildLeafListItem(container, property, valuePath, value, fieldId, index);
                        } else if (hasProperties) {
-                               field = buildElement(container, property, valuePath, value, key.join(':'))
+                               field = buildElement(container, property, valuePath, value, fieldId)
                        } else {
-                               field = buildField(container, property, valuePath, value, key.join(':'));
+                               field = buildField(container, property, valuePath, value, fieldId);
                        }
 
                        function onClickLeaf(property, path, value, event) {
@@ -568,7 +697,7 @@ export default function EditDescriptorModelProperties(props) {
                                event.preventDefault();
                                event.stopPropagation();
                                this.getRoot().uiState.focusedPropertyPath = path.join('.');
-                               console.log('property selected', path.join('.'));
+                               console.debug('property selected', path.join('.'));
                                ComposerAppActions.propertySelected([path.join('.')]);
                        }
 
@@ -576,10 +705,10 @@ export default function EditDescriptorModelProperties(props) {
                        const isContainerList = isArray && !(isSimpleListView || isLeafList);
 
                        fields.push(
-                               <div key={fieldKey.concat(['property-content', index]).join(':')}
+                               <div key={fieldId}
                                         className={ClassNames('property-content', {'simple-list': isSimpleListView})}
                                         onClick={clickHandler.bind(container, property, valuePath, value)}>
-                                       {isContainerList ? buildRemoveListItem(container, property, valuePath, fieldKey, index) : null}
+                                       {isContainerList ? buildRemoveListItem(container, property, valuePath, index) : null}
                                        {field}
                                </div>
                        );
@@ -608,9 +737,9 @@ export default function EditDescriptorModelProperties(props) {
                const onFocus = isLeaf ? event => event.target.classList.add('-is-focused') : false;
 
                return (
-                       <div key={fieldKey.join(':')} className={ClassNames(property.type + '-property property', classNames)} onFocus={onFocus}>
+                       <div key={uniqueId} className={ClassNames(property.type + '-property property', classNames)} onFocus={onFocus}>
                                <h3 className="property-label">
-                                       <label htmlFor={fieldKey.join(':')}>
+                                       <label htmlFor={uniqueId}>
                                                <span className={property.type + '-name name'}>{title}</span>
                                                <small>
                                                        <span className={property.type + '-info info'}>{displayValueInfo}</span>
@@ -697,5 +826,5 @@ export default function EditDescriptorModelProperties(props) {
                        {buildAdvancedGroup()}
                </div>
        );
+};
 
-}
index 3833ab4..ed9ea93 100644 (file)
@@ -18,7 +18,8 @@
 
 
 //https://raw.githubusercontent.com/RIFTIO/RIFT.ware/master/rift-shell
-import _ from 'lodash'
+import _cloneDeep from 'lodash/cloneDeep'
+import _findIndex from 'lodash/findIndex'
 import React from 'react';
 import ReactDOM from 'react-dom';
 import TreeView from 'react-treeview';
@@ -83,11 +84,11 @@ class FileManager extends React.Component {
             let splitUrl = url.split('/');
             let fileName = splitUrl[splitUrl.length - 1];
             folder.pop;
-            let fullPath = _.cloneDeep(folder);
+            let fullPath = _cloneDeep(folder);
             fullPath.push(fileName);
             fullPath = fullPath.join('/');
             folder = folder.join('/');
-            let fileIndex = _.findIndex(files[folder], function(f) {
+            let fileIndex = _findIndex(files[folder], function(f) {
                 return f.name == fullPath;
             })
             if (fileIndex == -1) {
@@ -123,7 +124,7 @@ class FileManager extends React.Component {
 function buildList(self, data) {
     let toReturn = [];
     data.id.map(function(k,i) {
-        toReturn.push (contentFolder(self, data.data[k], k, i, self.props.filesState, self.updateFileLocationInput, self.sendDownloadFileRequst, self.deleteFile));
+        toReturn.push (contentFolder(self, data.data[k], k, k+i, self.props.filesState, self.updateFileLocationInput, self.sendDownloadFileRequst, self.deleteFile));
     });
     return toReturn.reverse();
 }
@@ -131,7 +132,8 @@ function buildList(self, data) {
 function contentFolder(context, folder, path, key, inputState, updateFn, sendDownloadFileRequst, deleteFn) {
     let type = context.props.type;
     let id = context.props.item.id;
-    const onboardDropZone = createDropZone.bind(this, FileManagerUploadDropZone.ACTIONS.onboard, '.ComposerAppAddFile.' + path.replace(/\//g, '-'), type, id, path);
+    let classId = `DZ-${path.replace(/\/|\s+/g, '-')}`;
+    const onboardDropZone = createDropZone.bind(this, FileManagerUploadDropZone.ACTIONS.onboard, '.ComposerAppAddFile.' + classId, type, id, path);
     return (
         <Panel title={path} key={key} itemClassName="nested" no-corners>
         <div className="folder">
@@ -169,11 +171,12 @@ class ItemUpload extends React.Component {
     }
     render() {
         let {type, id, path, key, ...props} = this.props;
+        let classId = `DZ-${path.replace(/\/|\s+/g, '-')}`;
         return (
             <div className="inputSection">
                 <label className="sqTextInput" style={{flexDirection: 'row', alignItems:'center'}}>
                     <span>Upload File</span>
-                    <Button className={'ComposerAppAddFile ' + path.replace(/\//g, '-')} label="BROWSE"/>
+                    <Button className={'ComposerAppAddFile ' + classId} label="BROWSE"/>
                 </label>
             </div>
         )
index 325d026..1f0fd80 100644 (file)
@@ -18,7 +18,6 @@
  */
 'use strict';
 
-import _ from 'lodash'
 import $ from 'jquery'
 import alt from '../../alt'
 import utils from '../../libraries/utils'
@@ -101,16 +100,26 @@ const FileManagerSource = {
                         beforeSend: Utils.addAuthorizationStub,
                         url: 'api/file-manager?api_server=' + utils.getSearchParams(window.location).api_server +'&package_type=' + type + '&package_id=' + id + '&package_path=' + path ,
                         success: function(data) {
-                            resolve({
-                                data: data,
-                                path: path
-                            });
+                            if (data.output.status == 'True') {
+                                resolve({
+                                    data: data,
+                                    path: path
+                                });
+                            } else {
+                                reject({
+                                    data: data,
+                                    path: path
+                                })
+                            }
                         },
                         error: function(error) {
                             if (typeof error == 'string') {
                                 error = JSON.parse(error);
                             }
-                            reject(error);
+                            reject({
+                                path: path,
+                                data: error
+                            });
                         }
                     }).fail(function(xhr){
                         //Authentication and the handling of fail states should be wrapped up into a connection class.
@@ -146,11 +155,11 @@ const FileManagerSource = {
                 return new Promise(function(resolve, reject) {
                     //api/operational/download-jobs/job/
                    $.ajax({
-                    url: '/socket-polling',
+                    url: '/socket-polling?api_server=' + API_SERVER,
                     type: 'POST',
                     beforeSend: Utils.addAuthorizationStub,
                     data: {
-                      url: 'http://localhost:8000/composer/api/file-manager/jobs/' + packageID + '?api_server=' + API_SERVER,
+                      url: 'composer/api/file-manager/jobs/' + packageID + '?api_server=' + API_SERVER,
                     },
                     success: function(data, textStatus, jqXHR) {
                         Utils.checkAndResolveSocketRequest(data, resolve, reject);
@@ -171,11 +180,11 @@ const FileManagerSource = {
                 return new Promise(function(resolve, reject) {
                     //api/operational/download-jobs/job/
                    $.ajax({
-                    url: '/socket-polling',
+                    url: '/socket-polling?api_server=' + API_SERVER,
                     type: 'POST',
                     beforeSend: Utils.addAuthorizationStub,
                     data: {
-                      url: 'http://localhost:8000/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=' + id
                     },
                     success: function(data, textStatus, jqXHR) {
                         Utils.checkAndResolveSocketRequest(data, resolve, reject);
index 026555c..d5734df 100644 (file)
@@ -20,7 +20,6 @@
  * Created by onvelocity on 10/2/15.
  */
 
-import _ from 'lodash'
 import d3 from 'd3'
 import UID from './UniqueId'
 import SelectionManager from './SelectionManager'
index 94ab813..3089b82 100644 (file)
@@ -5,7 +5,6 @@
 
 'use strict';
 
-import _ from 'lodash'
 import d3 from 'd3'
 import UID from './UniqueId'
 import React from 'react'
index b70c921..69ae493 100644 (file)
@@ -21,7 +21,6 @@
  */
 'use strict';
 
-import _ from 'lodash'
 import d3 from 'd3'
 import math from './math'
 import ClassNames from 'classnames'
index 9274c23..0094e01 100644 (file)
@@ -4,7 +4,7 @@
 
 'use strict';
 
-import _ from 'lodash'
+import _isArray from 'lodash/isArray'
 import d3 from 'd3'
 
 /**
@@ -26,7 +26,7 @@ export default class HighlightRecordServicePaths {
                Array.from(document.querySelectorAll(`svg .forwarding-graph-paths`)).forEach(d => {
                        d3.select(d).classed('-is-highlighting', true);
                });
-               const list = _.isArray(rsp) ? rsp : [rsp];
+               const list = _isArray(rsp) ? rsp : [rsp];
                list.forEach(rsp => {
                        Array.from(document.querySelectorAll(`[data-id="${rsp.id}"]`)).forEach(d => {
                                d.parentNode.appendChild(d);
index f53b4dc..571fcdf 100644 (file)
@@ -2,7 +2,6 @@
  * Created by onvelocity on 2/10/16.
  */
 import alt from '../../../alt'
-import _ from 'lodash'
 import d3 from 'd3'
 import math from '../math'
 import ClassNames from 'classnames'
index 1a2ba4f..eb70e2a 100644 (file)
@@ -20,7 +20,7 @@
  * Created by onvelocity on 8/23/15.
  */
 
-import _ from 'lodash'
+import _isArray from 'lodash/isArray'
 import guid from '../guid'
 import Position from '../graph/Position'
 import IconFactory from './IconFactory'
@@ -279,7 +279,7 @@ export default class DescriptorModel {
 
        updateModelList(modelFieldName, modelFieldValue, descriptorClass = DescriptorModel, newItemAddedSuccessCallback = () => {}) {
                // value can be Array of (DescriptorModel | json model), DescriptorModel, or json model
-               if (_.isArray(modelFieldValue)) {
+               if (_isArray(modelFieldValue)) {
                        this.model[modelFieldName] = modelFieldValue.map(d => d instanceof descriptorClass ? d.model : d);
                        return true;
                }
index ac59872..6de5d21 100644 (file)
@@ -21,7 +21,8 @@
 
 'use strict';
 
-import _ from 'lodash'
+import _isEmpty from 'lodash/isEmpty'
+import _cloneDeep from 'lodash/cloneDeep'
 import d3 from 'd3'
 import UID from './../UniqueId'
 import guid from '../guid'
@@ -157,7 +158,7 @@ class DescriptorModelFactory {
                }
 
                return (containerList, obj) => {
-                       if (_.isEmpty(obj)) {
+                       if (_isEmpty(obj)) {
                                return containerList;
                        }
                        switch (obj.uiState.type) {
@@ -249,7 +250,7 @@ class DescriptorModelFactory {
                } else {
                        model = vnfdToWrap;
                }
-               return new VirtualNetworkFunctionReadOnlyWrapper(_.cloneDeep(model), parent);
+               return new VirtualNetworkFunctionReadOnlyWrapper(_cloneDeep(model), parent);
        }
 
        static newClassifier(model, parent) {
index d3ef200..69098ec 100644 (file)
@@ -4,10 +4,11 @@
  * This class provides methods to get the metadata about descriptor models.
  */
 
-'use strict';
-
-import _ from 'lodash'
-import utils from './../utils'
+import _cloneDeep from 'lodash/cloneDeep'
+import _isEmpty from 'lodash/isEmpty'
+import _pick from 'lodash/pick'
+import _get from 'lodash/get'
+import _set from 'lodash/set'
 import DescriptorModelMetaProperty from './DescriptorModelMetaProperty'
 import CommonUtils from 'utils/utils';
 const assign = Object.assign;
@@ -24,6 +25,156 @@ function getPathForType(type) {
        return type;
 }
 
+const uiStateToSave = ['containerPositionMap'];
+
+//////
+// Data serialization will be done on a meta model basis. That is,
+// given a schema and data, retrieve from the data only that which is 
+// defined by the schema.
+//
+
+// serialize data for a list of properties
+function serializeAll(properties, data) {
+       if (data) {
+               return properties.reduce((obj, p) => {
+                       return Object.assign(obj, p.serialize(data));
+               }, {});
+       }
+       return null;
+}
+
+function serialize_container(data) {
+       data = data[this.name];
+       if (_isEmpty(data)) {
+               return null;
+       }
+       let obj = {};
+       obj[this.name] = serializeAll(this.properties, data);
+       return obj;
+}
+
+function serialize_list(data) {
+       data = data[this.name];
+       if (data) {
+               if (!Array.isArray(data)) {
+                       return serializeAll(this.properties, data);
+               } else if (data.length) {
+                       let list = data.reduce((c, d) => {
+                               let obj = serializeAll(this.properties, d);
+                               if (!_isEmpty(obj)) {
+                                       c.push(obj);
+                               }
+                               return c;
+                       }, []);
+                       if (!_isEmpty(list)){
+                               let obj = {};
+                               obj[this.name] = list;
+                               return obj;
+                       }
+               }
+       }
+       return null;
+}
+
+function serialize_leaf(data) {
+       let value = data[this.name];
+       if (value === null || typeof value === 'undefined' || value === '') {
+               return null;
+       }
+       let obj = {};
+       if (this['data-type'] === 'empty') {
+               value = ''; // empty string does get sent as value
+       }
+       obj[this.name] = value;
+       return obj;
+}
+
+function serialize_leaf_empty(data) {
+       let value = data[this.name];
+       if (value) {
+               let obj = {};
+               obj[this.name] = "";
+               return obj;
+       }
+       return null;
+}
+
+function serialize_leaf_list(data) {
+       data = data[this.name];
+       if (data) {
+               commaSeparatedValues = data.reduce((d, v) => {
+                       let leaf = Serializer.leaf.call(this, d);
+                       let value = leaf & leaf[this.name];
+                       if (value && value.length) {
+                               if (v.length) {
+                                       v += ', ';
+                               }
+                               v += value;
+                       }
+               }, "");
+               if (commaSeparatedValues.length) {
+                       let obj = {};
+                       obj[this.name] = commaSeparatedValues;
+                       return obj;
+               }
+       }
+       return null;
+}
+
+function serialize_choice(data) {
+       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));
+               return chosen && serializeAll(chosen.properties, data);
+       }
+       return null;
+}
+
+function serialize_case(data) {
+       return Serializer.container.call(this, data);
+}
+
+// special ui data handler for leaf of type string named 'meta' 
+function serialize_meta(data) {
+       let uiState = data['uiState'];
+       let meta = uiState && _pick(uiState, uiStateToSave);
+       // if there is no uiState to save perhaps this was not a ui state property
+       return _isEmpty(meta) ? null : {meta: JSON.stringify(meta)};
+}
+
+function serialize_unsupported(data) {
+       console.error('unsupported property', property);
+       return null;
+}
+
+function getSerializer(property) {
+       switch (property.name) {
+               case 'rw-nsd:meta':
+               case 'rw-vnfd:meta':
+                       return serialize_meta.bind(property);
+       }
+       switch (property.type) {
+               case 'list':
+               return serialize_list.bind(property);
+               case 'container':
+               return serialize_container.bind(property);
+               case 'choice':
+               return serialize_choice.bind(property);
+               case 'case':
+               return serialize_case.bind(property);
+               case 'leaf_list':
+               return serialize_leaf_list.bind(property);
+               case 'leaf':
+               switch (property['data-type']){
+                       case 'empty':
+                       return serialize_leaf_empty.bind(property);
+               }
+               return serialize_leaf.bind(property);
+       }
+       return serialize_unsupported.bind(property);
+}
+
 let modelMetaByPropertyNameMap = [];
 
 let cachedDescriptorModelMetaRequest = null;
@@ -31,32 +182,38 @@ let cachedDescriptorModelMetaRequest = null;
 export default {
        init() {
                if (!cachedDescriptorModelMetaRequest) {
-                       cachedDescriptorModelMetaRequest = new Promise(function(resolve, reject) {
-                               CommonUtils.getDescriptorModelMeta().then(function(data) {
+                       cachedDescriptorModelMetaRequest = new Promise(function (resolve, reject) {
+                               CommonUtils.getDescriptorModelMeta().then(function (data) {
                                        let DescriptorModelMetaJSON = data;
                                        modelMetaByPropertyNameMap = Object.keys(DescriptorModelMetaJSON).reduce((map, key) => {
                                                function mapProperties(parentMap, parentObj) {
+                                                       // let's beef up the meta info with a helper (more to come?)
+                                                       parentObj.serialize = getSerializer(parentObj);
                                                        parentMap[':meta'] = parentObj;
                                                        const properties = parentObj && parentObj.properties ? parentObj.properties : [];
                                                        properties.forEach(p => {
-                                                               parentMap[p.name] = mapProperties({}, assign(p, {':qualified-type': parentObj[':qualified-type'] + '.' + p.name}));
+                                                               parentMap[p.name] = mapProperties({}, assign(p, {
+                                                                       ':qualified-type': parentObj[':qualified-type'] + '.' + p.name
+                                                               }));
                                                                return map;
                                                        }, parentMap);
                                                        return parentMap;
                                                }
-                                               map[key] = mapProperties({}, assign(DescriptorModelMetaJSON[key], {':qualified-type': key}));
+                                               map[key] = mapProperties({}, assign(DescriptorModelMetaJSON[key], {
+                                                       ':qualified-type': key
+                                               }));
                                                return map;
                                        }, {});
 
                                        (() => {
                                                // initialize the UI centric properties that CONFD could care less about
-                                               utils.assignPathValue(modelMetaByPropertyNameMap, 'nsd.meta.:meta.preserve-line-breaks', true);
-                                               utils.assignPathValue(modelMetaByPropertyNameMap, 'vnfd.meta.:meta.preserve-line-breaks', true);
-                                               utils.assignPathValue(modelMetaByPropertyNameMap, 'vnfd.vdu.cloud-init.:meta.preserve-line-breaks', true);
-                                               utils.assignPathValue(modelMetaByPropertyNameMap, 'nsd.constituent-vnfd.vnf-configuration.config-template.:meta.preserve-line-breaks', true);
+                                               _set(modelMetaByPropertyNameMap, 'nsd.meta.:meta.preserve-line-breaks', true);
+                                               _set(modelMetaByPropertyNameMap, 'vnfd.meta.:meta.preserve-line-breaks', true);
+                                               _set(modelMetaByPropertyNameMap, 'vnfd.vdu.cloud-init.:meta.preserve-line-breaks', true);
+                                               _set(modelMetaByPropertyNameMap, 'nsd.constituent-vnfd.vnf-configuration.config-template.:meta.preserve-line-breaks', true);
                                        })();
                                        resolve();
-                               }, function(error) {
+                               }, function (error) {
                                        cachedDescriptorModelMetaRequest = null;
                                })
                        })
@@ -64,15 +221,23 @@ export default {
 
                return cachedDescriptorModelMetaRequest;
        },
-       createModelInstanceForType(typeOrPath) {
+       /**
+        * Create a new instance of the indicated property and, if relevent, use the given
+        * unique name for the instance's key (see generateItemUniqueName)
+        * 
+        * @param {Object | string} typeOrPath a property definition object or a path to a property 
+        * @param [{string}] uniqueName optional 
+        * @returns 
+        */
+       createModelInstanceForType(typeOrPath, uniqueName) {
                const modelMeta = this.getModelMetaForType(typeOrPath);
-               return DescriptorModelMetaProperty.createModelInstance(modelMeta);
+               return DescriptorModelMetaProperty.createModelInstance(modelMeta, uniqueName);
        },
        getModelMetaForType(typeOrPath, filterProperties = () => true) {
                // resolve paths like 'nsd' or 'vnfd.vdu' or 'nsd.constituent-vnfd'
-               const found = utils.resolvePath(modelMetaByPropertyNameMap, getPathForType(typeOrPath));
+               const found = _get(modelMetaByPropertyNameMap, getPathForType(typeOrPath));
                if (found) {
-                       const uiState = _.cloneDeep(found[':meta']);
+                       const uiState = _cloneDeep(found[':meta']);
                        uiState.properties = uiState.properties.filter(filterProperties);
                        return uiState;
                }
@@ -80,23 +245,65 @@ export default {
        },
        getModelFieldNamesForType(typeOrPath) {
                // resolve paths like 'nsd' or 'vnfd.vdu' or 'nsd.constituent-vnfd'
-               const found = utils.resolvePath(modelMetaByPropertyNameMap, getPathForType(typeOrPath));
+               const found = _get(modelMetaByPropertyNameMap, getPathForType(typeOrPath));
                if (found) {
                        let result = [];
                        found[':meta'].properties.map((p) => {
                                // if(false) {
-                               if(p.type == 'choice') {
+                               if (p.type == 'choice') {
                                        result.push(p.name)
-                                       return p.properties.map(function(q){
+                                       return p.properties.map(function (q) {
                                                result.push(q.properties[0].name);
                                        })
 
-                               } else  {
+                               } else {
                                        return result.push(p.name);
                                }
                        })
                        return result;
                }
                console.warn('no model uiState found for type', typeOrPath);
+       },
+       /**
+        * For a list with a single valued key that is of type string, generate a unique name
+        * for a new entry to be added to the indicated list. This name will use the provided
+        * prefix (or the list's name) followed by a number. The number will be based on the
+        * current length of the array but will insure there is no collision with an existing
+        * name.
+        * 
+        * @param {Array} list the list model data
+        * @param {prooerty} property the schema definition of the list 
+        * @param [{any} prefix] the perferred prefix for the name. If not provide property.name
+        *  will be used. 
+        * @returns {string}
+        */
+       generateItemUniqueName(list, property, prefix) {
+               if (property.type !== 'list' ||
+                       property.key.length !== 1 ||
+                       property.properties.find(prop => prop.name === property.key[0])['data-type'] !== 'string') {
+                       // only support list with a single key of type string
+                       return null;
+               }
+               if (!prefix) {
+                       prefix = property.name;
+               }
+               let key = property.key[0];
+               let suffix = list ? list.length + 1 : 1
+               let keyValue = prefix + '-' + suffix;
+
+               function makeUniqueName() {
+                       if (list) {
+                               for (let i = 0; i < list.length; i = ++i) {
+                                       if (list[i][key] === keyValue) {
+                                               keyValue = keyValue + '-' + (i + 1);
+                                               makeUniqueName(); // not worried about recursing too deep (chances ??)
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               makeUniqueName();
+               return keyValue;
        }
-}
+
+}
\ No newline at end of file
index 8fe51d9..e064457 100644 (file)
@@ -21,9 +21,8 @@
  * This class provides utility methods for interrogating an instance of model uiState object.
  */
 
-'use strict';
-
-import _ from 'lodash'
+import _includes from 'lodash/includes'
+import _isArray from 'lodash/isArray'
 import guid from './../guid'
 import changeCase from 'change-case'
 import InstanceCounter from './../InstanceCounter'
@@ -35,6 +34,9 @@ export default {
        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 /leaf|choice/.test(property.type);
        },
@@ -69,7 +71,7 @@ export default {
         return !/^(leaf|leaf_list)$/.test(property.type);
        },
        isSimpleList(property = {}) {
-               return _.includes(DescriptorModelFields.simpleList, property.name);
+               return _includes(DescriptorModelFields.simpleList, property.name);
        },
        isPrimativeDataType(property = {}) {
                const Property = this;
@@ -146,22 +148,36 @@ export default {
                }
                return /uuid/.test(property['data-type']);
        },
-       createModelInstance(property) {
+       /**
+        * Create a new instance of the indicated property and, if relevent, use the given
+        * unique name for the instance's key (see generateItemUniqueName)
+        * 
+        * @param {Object} typeOrPath - property definition
+        * @param {any} uniqueName 
+        * @returns 
+        */
+       createModelInstance(property, uniqueName) {
                const Property = this;
                const defaultValue = Property.defaultValue.bind(this);
-               function createModel(uiState, parentMeta) {
+               function createModel(uiState, parentMeta, uniqueName) {
                        const model = {};
                        if (Property.isLeaf(uiState)) {
                                if (uiState.name === 'name') {
-                                       return changeCase.param(parentMeta.name) + '-' + InstanceCounter.count(parentMeta[':qualified-type']);
+                                       return uniqueName || (changeCase.param(parentMeta.name) + '-' + InstanceCounter.count(parentMeta[':qualified-type']));
                                }
-                               if (_.isArray(parentMeta.key) && _.includes(parentMeta.key, uiState.name)) {
+                               if (_isArray(parentMeta.key) && _includes(parentMeta.key, uiState.name)) {
                                        if (/uuid/.test(uiState['data-type'])) {
                                                return guid();
                                        }
                                        if (uiState['data-type'] === 'string') {
-                                               const prefix = uiState.name.replace('id', '');
-                                               return  (prefix ? changeCase.param(prefix) + '-' : '') + guid(5);
+                                               // if there is only one key property and we were given a 
+                                               // unique name (probably because creating a list entry
+                                               // property) then use the unique name otherwise make one up.
+                                               if (parentMeta.key.length > 1 || !uniqueName) {
+                                                       const prefix = uiState.name.replace('id', '');
+                                                       uniqueName = (prefix ? changeCase.param(prefix) + '-' : '') + guid(5);
+                                               }
+                                               return uniqueName;
                                        }
                                        if (/int/.test(uiState['data-type'])) {
                                                return InstanceCounter.count(uiState[':qualified-type']);
@@ -172,7 +188,7 @@ export default {
                                return [];
                        } else {
                                uiState.properties.forEach(p => {
-                                       model[p.name] = createModel(p, uiState);
+                                       model[p.name] = createModel(p, uiState, uniqueName);
                                });
                        }
                        return model;
@@ -187,7 +203,7 @@ export default {
                        if (/list/.test(property.type)) {
                                property.type = 'container';
                        }
-                       const modelInstance = createModel(property, property);
+                       const modelInstance = createModel(property, property, uniqueName);
                        modelInstance.uiState = {type: property.name};
                        const modelFragment = DescriptorTemplateFactory.createModelForType(property[':qualified-type'] || property.name) || {};
                        Object.assign(modelInstance, modelFragment);
index 8ce90cf..b496041 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  *
  *   Copyright 2016 RIFT.IO Inc
  * Created by onvelocity on 10/20/15.
  */
 
-import _ from 'lodash'
-import utils from './../utils'
-import DescriptorModelFields from './DescriptorModelFields'
 import DescriptorModelMetaFactory from './DescriptorModelMetaFactory'
 
-let nsdFields = null;
-let vldFields = null;
-let vnfdFields = null;
-let cvnfdFields = null;
-
-
-
-
-/**
- * Serialize DescriptorModel JSON into CONFD JSON. Also, cleans up the data as needed.
- *
- * @type {{serialize: (function(*=)), ':clean': (function(*=)), nsd: {serialize: (function(*=))}, vld: {serialize: (function(*=))}, vnfd-connection-point-ref: {serialize: (function(*=))}, constituent-vnfd: {serialize: (function(*=))}, vnfd: {serialize: (function(*=))}, vdu: {serialize: (function(*=))}}}
- */
 const DescriptorModelSerializer = {
+       /**
+        * Create a json object that can be sent to the backend. I.e. CONFD JSON compliant to the schema definition.
+        * 
+        * @param {any} model - the data blob from the editor. This is not modified.
+        * @returns cleansed data model
+        */
        serialize(model) {
-               const type = model.uiState && model.uiState.type;
-               const serializer = this[type];
-               if (serializer) {
-                       model = serializer.serialize(model);
-                       this[':clean'](model);
-                       return model;
-               }
-               return false;
-       },
-       ':clean'(model) {
-               // remove uiState from all elements accept nsd and vnfd
-               // remove empty / blank value fields
-               function clean(m) {
-                       Object.keys(m).forEach(k => {
-                               const isEmptyObject = typeof m[k] === 'object' && _.isEmpty(m[k]);
-                               if (typeof m[k] === 'undefined' || isEmptyObject || m[k] === '') {
-                                       delete m[k];
-                               }
-                               const isMetaAllowed = /^nsd|vnfd$/.test(m.uiState && m.uiState.type);
-                               if (k === 'uiState') {
-                                       if (isMetaAllowed) {
-                                               // remove any transient ui state properties
-                                               const uiState = _.pick(m.uiState, DescriptorModelFields.meta);
-                                               if (!_.isEmpty(uiState)) {
-                                                       // uiState field must be a string
-                                                       m['meta'] = JSON.stringify(uiState);
-                                               }
-                                       }
-                                       delete m[k];
-                               }
-                               if (typeof m[k] === 'object') {
-                                       clean(m[k]);
-                               }
-                       });
-               }
-               clean(model);
-               return model;
-       },
-       nsd: {
-               serialize(nsdModel) {
-                       if(!nsdFields) nsdFields = DescriptorModelMetaFactory.getModelFieldNamesForType('nsd').concat('uiState');
-                       const confd = _.pick(nsdModel, nsdFields);
-
-                       // vnfd is defined in the ETSI etsi_gs reference manual but RIFT does not use it
-                       delete confd.vnfd;
-
-                       // map the vnfd instances into the CONFD constituent-vnfd ref instances
-                       confd['constituent-vnfd'] = confd['constituent-vnfd'].map((d, index) => {
-
-                               const constituentVNFD = {
-                                       'member-vnf-index': d['member-vnf-index'],
-                                       'vnfd-id-ref': d['vnfd-id-ref']
-                               };
-
-                               if (d['vnf-configuration']) {
-                                       const vnfConfig = _.cloneDeep(d['vnf-configuration']);
-                                       const configType = vnfConfig['config-type'] || 'none';
-                                       // make sure we send the correct values based on config type
-                                       if (configType === 'none') {
-                                               constituentVNFD['vnf-configuration'] = {'config-type': 'none'};
-                                               const configPriority = utils.resolvePath(vnfConfig, 'input-params.config-priority');
-                                               const configPriorityValue = _.isNumber(configPriority) ? configPriority : d.uiState['member-vnf-index'];
-                                               utils.assignPathValue(constituentVNFD['vnf-configuration'], 'input-params.config-priority', configPriorityValue);
-                                       } else {
-                                               // remove any unused configuration options
-                                               ['netconf', 'rest', 'script', 'juju'].forEach(type => {
-                                                       if (configType !== type) {
-                                                               delete vnfConfig[type];
-                                                       }
-                                               });
-                                               constituentVNFD['vnf-configuration'] = vnfConfig;
-                                       }
-                               }
-
-                               if (d.hasOwnProperty('start-by-default')) {
-                                       constituentVNFD['start-by-default'] = d['start-by-default'];
-                               }
-
-                               return constituentVNFD;
-
-                       });
-                       for (var key in confd) {
-                               checkForChoiceAndRemove(key, confd, nsdModel);
-                       }
-                       // serialize the VLD instances
-                       confd.vld = confd.vld.map(d => {
-                               return DescriptorModelSerializer.serialize(d);
-                       });
-
-                       return cleanEmptyTopKeys(confd);
-
-               }
-       },
-       vld: {
-               serialize(vldModel) {
-                       if(!vldFields) vldFields = DescriptorModelMetaFactory.getModelFieldNamesForType('nsd.vld');
-                       const confd = _.pick(vldModel, vldFields);
-                       const property = 'vnfd-connection-point-ref';
-
-                       // TODO: There is a bug in RIFT-REST that is not accepting empty
-                       // strings for string properties.
-                       // once that is fixed, remove this piece of code.
-                       // fix-start
-                       for (var key in confd) {
-                               if (confd.hasOwnProperty(key) && confd[key] === '') {
-                       delete confd[key];
-                } else {
-                       //removes choice properties from top level object and copies immediate children onto it.
-                                       checkForChoiceAndRemove(key, confd, vldModel);
-                }
-                       }
-
-
-                       const deepProperty = 'provider-network';
-                       for (var key in confd[deepProperty]) {
-                               if (confd[deepProperty].hasOwnProperty(key) && confd[deepProperty][key] === '') {
-                                       delete confd[deepProperty][key];
-                               }
-                       }
-                       // fix-end
-                       confd[property] = confd[property].map(d => DescriptorModelSerializer[property].serialize(d));
-                       return cleanEmptyTopKeys(confd);
-               }
-       },
-       'vnfd-connection-point-ref': {
-               serialize(ref) {
-                       return _.pick(ref, ['member-vnf-index-ref', 'vnfd-id-ref', 'vnfd-connection-point-ref']);
-               }
-       },
-       'internal-connection-point': {
-               serialize(ref) {
-                       return _.pick(ref, ['id-ref']);
-               }
-       },
-       'constituent-vnfd': {
-               serialize(cvnfdModel) {
-                       if(!cvnfdFields) cvnfdFields = DescriptorModelMetaFactory.getModelFieldNamesForType('nsd.constituent-vnfd');
-                       return _.pick(cvnfdModel, cvnfdFields);
-               }
-       },
-       vnfd: {
-               serialize(vnfdModel) {
-                       if(!vnfdFields) vnfdFields = DescriptorModelMetaFactory.getModelFieldNamesForType('vnfd').concat('uiState');
-                       const confd = _.pick(vnfdModel, vnfdFields);
-                       confd.vdu = confd.vdu.map(d => DescriptorModelSerializer.serialize(d));
-                       return cleanEmptyTopKeys(confd);
-               }
-       },
-       vdu: {
-               serialize(vduModel) {
-                       const copy = _.cloneDeep(vduModel);
-                       for (let k in copy) {
-                               checkForChoiceAndRemove(k, copy, vduModel)
-                       }
-                       const confd = _.omit(copy, ['uiState']);
-                       return cleanEmptyTopKeys(confd);
-               }
+               if (!model.uiState) {
+                       console.error('model uiState null', model);
+                       return {};
+               }
+               const path = model.uiState['qualified-type'] || model.uiState['type'];
+               const metaModel = DescriptorModelMetaFactory.getModelMetaForType(path);
+               const data = {};
+               const name = model.uiState['type'];
+               data[name] = model; // lets get the meta hierachy from the top
+               const result = metaModel.serialize(data);
+               console.debug(result);
+               return result;
        }
-};
-
-
-function checkForChoiceAndRemove(k, confd, model) {
-    let state = model.uiState;
-    if (state.choice) {
-        let choice = state.choice[k]
-        if(choice) {
-            if (choice.constructor.name == "Array") {
-                for(let i = 0; i < choice.length; i++) {
-                    for (let key in confd[k][i]) {
-                        if(choice[i] && (choice[i].selected.indexOf(key) > -1)) {
-                            confd[k][i][key] = confd[k][i][key]
-                        }
-                        confd[key];
-                    };
-                }
-            } else {
-                for (let key in confd[k]) {
-                    if(choice && (choice.selected.indexOf(key) > -1)) {
-                        confd[key] = confd[k][key]
-                    }
-                };
-                delete confd[k];
-            }
-
-        }
-    }
-    return confd;
-}
-
-function cleanEmptyTopKeys(m){
-    Object.keys(m).forEach(k => {
-        const isEmptyObject = typeof m[k] === 'object' && _.isEmpty(m[k]);
-        if (typeof m[k] === 'undefined' || isEmptyObject || m[k] === '') {
-            delete m[k];
-        }
-    });
-    return m;
 }
-
-export default DescriptorModelSerializer;
+export default DescriptorModelSerializer;
\ No newline at end of file
index 28e7480..2946f18 100644 (file)
 
 'use strict';
 
-import _ from 'lodash'
+import _isFunction from 'lodash/isFunction'
+import _isArray from 'lodash/isArray'
+import _isObject from 'lodash/isObject'
+import _cloneDeep from 'lodash/cloneDeep'
 import DescriptorTemplates from './DescriptorTemplates'
 import DescriptorModelMetaFactory from './DescriptorModelMetaFactory'
 
@@ -33,13 +36,13 @@ function resolveInitHandlers(model) {
        function init(m) {
                keys(m).map(key => {
                        const value = m[key];
-                       if (_.isFunction(value)) {
+                       if (_isFunction(value)) {
                                m[key] = value(m, key, model);
                        }
-                       if (_.isArray(value)) {
+                       if (_isArray(value)) {
                                value.forEach(v => init(v));
                        }
-                       if (_.isObject(value)) {
+                       if (_isObject(value)) {
                                init(value);
                        }
                });
@@ -52,7 +55,7 @@ export default {
        createModelForType(type) {
                const template = DescriptorTemplates[type];
                if (template) {
-                       const model = _.cloneDeep(template);
+                       const model = _cloneDeep(template);
                        return resolveInitHandlers(model);
                }
        }
index 89e0a87..1183d4e 100644 (file)
 
 'use strict';
 
-import guid from './../guid'
-import InstanceCounter from './../InstanceCounter'
-
+//
+// note: values can be expressions. After the object is created the funtion will be 
+// invoked. if you use the arrow function syntax the this pointer will reference
+// the created object.
+//
 export default {
        'vnfd': {
-               'id': '5b9af24e-2c8f-4792-9d6e-ff9eabb97f15',
-               'name': 'vnfd-1',
-               'short-name': 'vnfd-1',
                'description': 'A simple VNF descriptor w/ one VDU',
                'version': '1.0',
                'connection-point': [
                        {
-                               'name': 'cp1',
+                               'name': 'connection-point-1',
                                'type': 'VPORT'
                        }
                ],
                'vdu': [
                        {
-                               'id': 'abd6831e-f811-4580-9aad-1de9c6424180',
+                               'uiState': {
+                                       'type': 'vdu'
+                               },
+                               'id': 'vdu-1',
                                'name': 'vdu-1',
                                'vm-flavor': {
                                        'vcpu-count': 4,
@@ -52,7 +54,7 @@ export default {
                                'external-interface': [
                                        {
                                                'name': 'eth0',
-                                               'vnfd-connection-point-ref': 'cp1',
+                                               'vnfd-connection-point-ref': 'connection-point-1',
                                                'virtual-interface': {
                                                        'type': 'VIRTIO'
                                                }
@@ -62,8 +64,6 @@ export default {
                ]
        },
        'vnfd.internal-vld': {
-               'id': () => guid(),
-               'name': () => 'vld-' + InstanceCounter.count('new.vnfd.internal-vld'),
                'description': 'Virtual link for internal fabric',
                'type': 'ELAN'
        }
index ef99af6..1173296 100644 (file)
@@ -21,7 +21,6 @@
 
 'use strict';
 
-import _ from 'lodash'
 import Classifier from './Classifier'
 import DescriptorModel from '../DescriptorModel'
 import RecordServicePath from './RecordServicePath'
@@ -57,7 +56,9 @@ export default class ForwardingGraph extends DescriptorModel {
        }
 
        createRsp() {
-               const model = DescriptorModelMetaFactory.createModelInstanceForType('nsd.vnffgd.rsp');
+               const property = DescriptorModelMetaFactory.getModelMetaForType('nsd.vnffgd.rsp');
+               const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(this.rsp, property);
+               const model = DescriptorModelMetaFactory.createModelInstanceForType('nsd.vnffgd.rsp', uniqueName);
                return this.rsp = new RecordServicePath(model, this);
        }
 
index 16fb159..b10fceb 100644 (file)
@@ -21,7 +21,6 @@
 
 'use strict';
 
-import _ from 'lodash'
 import ColorGroups from '../../ColorGroups'
 import DescriptorModel from '../DescriptorModel'
 import ForwardingGraph from './ForwardingGraph'
@@ -119,7 +118,9 @@ export default class NetworkService extends DescriptorModel {
        }
 
        createVld() {
-               const model = DescriptorModelMetaFactory.createModelInstanceForType('nsd.vld');
+               const property = DescriptorModelMetaFactory.getModelMetaForType('nsd.vld');
+               const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(this.vld, property);
+               const model = DescriptorModelMetaFactory.createModelInstanceForType('nsd.vld', uniqueName);
                return this.vld = DescriptorModelFactory.newVirtualLink(model, this);
        }
 
@@ -139,15 +140,13 @@ export default class NetworkService extends DescriptorModel {
        }
 
        set vnffgd(obj) {
-               const onAddForwardingGraph = (fg) => {
-                       const index = this.vnffgd.map(suffixAsInteger('short-name')).reduce(toBiggestValue, this.vnffgd.length);
-                       fg.model['short-name'] = 'FG-' + index;
-               };
-               this.updateModelList('vnffgd', obj, ForwardingGraph, onAddForwardingGraph);
+               this.updateModelList('vnffgd', obj, ForwardingGraph);
        }
 
        createVnffgd(model) {
-               model = model || DescriptorModelMetaFactory.createModelInstanceForType('nsd.vnffgd');
+               const property = DescriptorModelMetaFactory.getModelMetaForType('nsd.vnffgd');
+               const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(this.vnffgd, property, 'fg');
+               model = model || DescriptorModelMetaFactory.createModelInstanceForType('nsd.vnffgd', uniqueName);
                return this.vnffgd = DescriptorModelFactory.newForwardingGraph(model, this);
        }
 
index 8c43f10..7c2b631 100644 (file)
@@ -21,7 +21,6 @@
 
 'use strict';
 
-import _ from 'lodash'
 import DescriptorModel from '../DescriptorModel'
 import RspConnectionPointRef from './RspConnectionPointRef'
 import DescriptorModelFactory from '../DescriptorModelFactory'
index 73e10d3..5f3ad04 100644 (file)
@@ -52,7 +52,9 @@ export default class VirtualNetworkFunction extends DescriptorModel {
        }
 
        createVdu() {
-               const model = DescriptorModelMetaFactory.createModelInstanceForType('vnfd.vdu');
+               const property = DescriptorModelMetaFactory.getModelMetaForType('vnfd.vdu');
+               const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(this.vdu, property);
+               const model = DescriptorModelMetaFactory.createModelInstanceForType('vnfd.vdu', uniqueName);
                return this.vdu = DescriptorModelFactory.newVirtualDeploymentUnit(model, this);
        }
 
@@ -71,7 +73,9 @@ export default class VirtualNetworkFunction extends DescriptorModel {
        }
 
        createVld() {
-               const model = DescriptorModelMetaFactory.createModelInstanceForType('vnfd.internal-vld');
+               const property = DescriptorModelMetaFactory.getModelMetaForType('vnfd.internal-vld');
+               const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(this['internal-vld'], property);
+               const model = DescriptorModelMetaFactory.createModelInstanceForType('vnfd.internal-vld', uniqueName);
                return this.vld = DescriptorModelFactory.newInternalVirtualLink(model, this);
        }
 
index ff2adc0..d39a138 100644 (file)
 
 'use strict';
 
-import changeCase from 'change-case';
+import _cloneDeep from 'lodash/cloneDeep';
+import _isArray from 'lodash/isArray';
+import _map from 'lodash/map';
+import _flatten from 'lodash/flatten';
+import _find from 'lodash/find';
+import _toNumber from 'lodash/toNumber';
+import _isNumber from 'lodash/isNumber';
+import _clone from 'lodash/clone';
 
 export default {
        addAuthorizationStub(xhr) {
@@ -133,22 +140,22 @@ export default {
        },
 
        getResults (topLevelObject, pathArray) {
-               let objectCopy = _.cloneDeep(topLevelObject);
+               let objectCopy = _cloneDeep(topLevelObject);
                let i = pathArray.length;
                let results = [];
 
                while(pathArray[pathArray.length - i]) {
-                       if (_.isArray(objectCopy[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]);
+                                       results = _map(objectCopy[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]);
                                } else {
                                        objectCopy = objectCopy[pathArray[pathArray.length - i]];
                                }
-                       } else if (_.isArray(objectCopy)) {
+                       } else if (_isArray(objectCopy)) {
                                objectCopy.map((object) => {
-                                       if (_.isArray(object[pathArray[pathArray.length - i]])) {
+                                       if (_isArray(object[pathArray[pathArray.length - i]])) {
                                                if (i == 2) {
-                                                       results = results.concat(_.map(object[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]));
+                                                       results = results.concat(_map(object[pathArray[pathArray.length - i]], pathArray[pathArray.length - 1]));
                                                }
                                        }
                                })
@@ -161,7 +168,7 @@ export default {
 
        getAbsoluteResults (topLevelObject, pathArray) {
                let i = pathArray.length;
-               let objectCopy = _.cloneDeep(topLevelObject);
+               let objectCopy = _cloneDeep(topLevelObject);
                let results = [];
 
                let fragment = pathArray[pathArray.length - i]
@@ -169,9 +176,9 @@ export default {
                while (fragment) {
                        if (i == 1) {
                                // last fragment
-                               if (_.isArray(objectCopy)) {
+                               if (_isArray(objectCopy)) {
                                        // results will be obtained from a map
-                                       results = _.map(objectCopy, fragment);
+                                       results = _map(objectCopy, fragment);
                                } else {
                                        // object
                                        if (fragment.match(/\[.*\]/g)) {
@@ -187,16 +194,16 @@ export default {
                                        }
                                }
                        } else {
-                               if (_.isArray(objectCopy)) {
+                               if (_isArray(objectCopy)) {
                                        // is array
-                                       objectCopy = _.map(objectCopy, fragment);
+                                       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);
+                                               if (_isArray(objectCopy[i])) {
+                                                       objectCopy = _flatten(objectCopy);
                                                        console.log('This might be a bad leafref. Verify with backend team.')
                                                        break;
                                                }
@@ -215,7 +222,7 @@ export default {
                                                let key = fragment.split('[')[0];
                                                let searchObject = {};
                                                searchObject[predicateKey] = predicateValue;
-                                               let found = _.find(objectCopy[key], searchObject);
+                                               let found = _find(objectCopy[key], searchObject);
                                                if (found) {
                                                        objectCopy = found;
                                                } else {
@@ -225,10 +232,10 @@ export default {
                                                                predicateValue != NaN &&
                                                                predicateValue != Infinity &&
                                                                predicateValue != -Infinity) {
-                                                               let numericalPredicateValue = _.toNumber(predicateValue);
-                                                               if (_.isNumber(numericalPredicateValue)) {
+                                                               let numericalPredicateValue = _toNumber(predicateValue);
+                                                               if (_isNumber(numericalPredicateValue)) {
                                                                        searchObject[predicateKey] = numericalPredicateValue;
-                                                                       found = _.find(objectCopy[key], searchObject);
+                                                                       found = _find(objectCopy[key], searchObject);
                                                                }
                                                        }
                                                        if (found) {
@@ -243,6 +250,10 @@ export default {
                                                        break;
                                                }
                                                objectCopy = objectCopy[fragment];
+                                               if (!objectCopy) {
+                                                       // contains no value
+                                                       break;
+                                               }
                                        }
                                }
                        }
@@ -279,7 +290,7 @@ export default {
        },
 
        resolveLeafRefPath (catalogs, leafRefPath, fieldKey, path, container) {
-               let pathCopy = _.clone(path);
+               let pathCopy = _clone(path);
                // Strip any prefixes
                let leafRefPathCopy = leafRefPath.replace(/[\w\d]*:/g, '');
                // Strip any spaces
@@ -312,7 +323,7 @@ export default {
                        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;
@@ -322,13 +333,43 @@ export default {
                        } else if (fieldKeyArray.length == 2) {
                                for (let key in catalogs) {
                                        for (let subKey in catalogs[key]) {
-                                               let found = _.find(catalogs[key][subKey], {id: fieldKeyArray[0]});
+                                               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 = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
+                                                                               return results;
+                                                                       }
+                                                               } else {
+                                                                       if (foundKey == fieldKeyArray[1]) {
+                                                                               results = this.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) {
-                                                               let topLevel = _.find(found[foundKey], {id: fieldKeyArray[1]});
-                                                               if (topLevel) {
-                                                                       results = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
-                                                                       return results;
+                                                               if (_isArray(found[foundKey])) {
+                                                                       let topLevel = _find(found[foundKey], {id: fieldKeyArray[1]});
+                                                                       if (topLevel) {
+                                                                               results = this.getAbsoluteResults(topLevel, pathArray.splice(-i, i));
+                                                                               return results;
+                                                                       }
+                                                               } else {
+                                                                       if (foundKey == fieldKeyArray[1]) {
+                                                                               results = this.getAbsoluteResults(found[foundKey], pathArray.splice(-i, i));
+                                                                               return results;
+                                                                       }
                                                                }
                                                        }
                                                }
index e742a9e..9c297ca 100644 (file)
@@ -18,7 +18,6 @@
  */
 'use strict';
 
-import _ from 'lodash'
 import $ from 'jquery'
 import alt from '../alt'
 import utils from '../libraries/utils'
index 48d697b..e059f94 100644 (file)
  */
 'use strict';
 
-import $ from 'jquery'
 import alt from '../alt'
-import utils from '../libraries/utils'
+import catalogUtils from '../libraries/utils'
 import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions'
-let Utils = require('utils/utils.js');
-function getApiServerOrigin() {
-       return utils.getSearchParams(window.location).upload_server + ':4567';
-}
+import Utils from 'utils/utils.js';
 
-function ajaxRequest(path, catalogPackage, resolve, reject, method = 'GET', input, urlOverride) {
-       let options = {
-               url: getApiServerOrigin() + path,
-               type: method,
-               beforeSend: Utils.addAuthorizationStub,
-               dataType: 'json',
-               success: function(data) {
-                       if (typeof data == 'string') {
-                               data = JSON.parse(data);
-                       }
-                       resolve({
-                               data: data,
-                               state: catalogPackage
-                       });
-               },
-               error: function(error) {
-                       if (typeof error == 'string') {
-                               error = JSON.parse(error);
-                       }
-                       reject({
-                               data: error,
-                               state: catalogPackage
-                       });
-               }
+const getAuthorization = () => 'Basic ' + window.sessionStorage.getItem("auth");
+
+const getStateApiPath = (operation, id) => 
+       catalogUtils.getSearchParams(window.location).upload_server + ':4567/api/' + operation + '/' + id + '/state';
+
+const getComposerApiPath = (api) =>
+       window.location.origin + '/composer/api/' + api + '?api_server=' + catalogUtils.getSearchParams(window.location).api_server;
+
+const SUCCESS = {
+               pending: false,
+               success: true,
+               error: false,
+               message: "Completely successfully"
        };
-       if(input) {
-               options.data = input;
+const FAILED = {
+               pending: false,
+               success: false,
+               error: true,
+               message: "Failed"
+       };
+const PENDING = {
+               pending: true,
+               success: false,
+               error: false,
+               message: "In progress"
+       };
+
+function ajaxFetch(url, operation, resolve, reject, method = 'GET', input, urlOverride) {
+       let credentials = 'same-origin';
+       let body = input ? JSON.stringify(input) : null;
+       let headers = new Headers();
+       headers.append('Authorization', getAuthorization());
+       headers.append('Accept', 'application/json');
+       if (input) {
+               headers.append('Content-Type', 'application/json');
        }
-       if (urlOverride) {
-               options.url = window.location.origin + path;
+
+       fetch(url, {method, credentials, headers, body})
+               .then(checkStatusGetJson)
+               .then(handleSuccess)
+               .catch(handleError);
+
+       function checkStatusGetJson(response) {
+               if (response.status >= 200 && response.status < 300) {
+                       return response.json();
+               } else {
+                       var error = new Error(response.statusText)
+                       error.status = response.status;
+                       error.statusText = response.statusText;
+                       throw error
+               }
        }
-       $.ajax(options).fail(function(xhr){
-                                   //Authentication and the handling of fail states should be wrapped up into a connection class.
-                                   Utils.checkAuthentication(xhr.status);
-                               });
-}
 
+       function handleSuccess (data) {
+               if (typeof data == 'string') {
+                       data = JSON.parse(data);
+               }
+               resolve({
+                       state: operation,
+                       data,
+                       operation
+               });
+       }
 
+       function handleError (data) {
+               if (typeof data == 'string') {
+                       data = JSON.parse(data);
+               }
+               reject({
+                       state: operation,
+                       data,
+                       operation
+               });
+       }
+}
 
 const CatalogPackageManagerSource = {
 
-               requestCatalogPackageDownload: function () {
+       requestCatalogPackageDownload: function () {
                return {
                        remote: function (state, download, format, grammar, schema) {
                                return new Promise((resolve, reject) => {
-                                       // the server does not add a status in the payload
-                                       // so we add one so that the success handler will
-                                       // be able to follow the flow of this download
-                                       const setStatusBeforeResolve = (response = {}) => {
+                                       // we need an initial status for UI (server does not send)
+                                       const setStatusBeforeResolve = (response) => {
                                                response.data.status = 'download-requested';
                                                resolve(response);
                                        };
-                                       // RIFT-13485 requires to send type (nsd/vnfd) as a path element.
-                                       // Backend no longer supports mixed multi-package download.
-                                       // Probably does not even support multi-package download of same type.
-                                       // Hence, pick the type from the first element.
                                        const data = {
                                                "package-type": download['catalogItems'][0]['uiState']['type'].toUpperCase(),
                                                "package-id": download.ids,
@@ -90,13 +117,9 @@ const CatalogPackageManagerSource = {
                                                "export-grammar": grammar && grammar.toUpperCase() || 'OSM',
                                                "export-schema": schema && schema.toUpperCase() || "RIFT"
                                        }
-                                       const path = "/composer/api/package-export?api_server=" + utils.getSearchParams(window.location).api_server;
-                                       ajaxRequest(path, download, setStatusBeforeResolve, reject, 'POST', data, true);
+                                       const path = getComposerApiPath('package-export');
+                                       ajaxFetch(path, download, setStatusBeforeResolve, reject, 'POST', data, true);
                                })
-                               //.then(function(data) {
-                               //      let filename = data.data.output.filename;
-                               //      window.open(getApiServerOrigin() + "/api/export/" + filename, "_blank")
-                               //});
                        },
                        success: CatalogPackageManagerActions.downloadCatalogPackageStatusUpdated,
                        error: CatalogPackageManagerActions.downloadCatalogPackageError
@@ -108,8 +131,8 @@ const CatalogPackageManagerSource = {
                        remote: function(state, download) {
                                const transactionId = download.transactionId;
                                return new Promise(function(resolve, reject) {
-                                       const path = '/api/export/' + transactionId + '/state';
-                                       ajaxRequest(path, download, resolve, reject);
+                                       const path = getStateApiPath('export', transactionId);
+                                       ajaxFetch(path, download, resolve, reject);
                                });
                        },
                        success: CatalogPackageManagerActions.downloadCatalogPackageStatusUpdated,
@@ -117,14 +140,68 @@ const CatalogPackageManagerSource = {
                }
        },
 
+       requestCatalogPackageCopy: function () {
+               return {
+                       remote: function (state, operationInfo) {
+                               return new Promise((resolve, reject) => {
+                                       // we need an initial status for UI (server does not send)
+                                       const successHandler = (response) => {
+                                               const status = response.data.output.status;
+                                               const state = status === "COMPLETED" ? SUCCESS : status === "FAILED" ? FAILED : PENDING;
+                                               state.progress = 25; // put something
+                                               let operation = Object.assign({}, operationInfo, state);
+                                               operation.transactionId = response.data.output['transaction-id'];
+                                               resolve(operation);
+                                       }
+                                       const failHandler = (response) => {
+                                               let operation = Object.assign({}, this, FAILED);
+                                               reject(operation);
+                                       };
+                                       const data = {
+                                               "package-type": operationInfo.args.packageType,
+                                               "package-id": operationInfo.args.id,
+                                               "package-name": operationInfo.args.name
+                                       }
+                                       const path = getComposerApiPath('package-copy');
+                                       ajaxFetch(path, operationInfo, successHandler, failHandler, 'POST', data, true);
+                               })
+                       },
+                       success: CatalogPackageManagerActions.updateStatus,
+                       error: CatalogPackageManagerActions.updateStatus
+               };
+       },
+
+       requestCatalogPackageCopyStatus: function() {
+               return {
+                       remote: function(state, operation) {
+                               return new Promise(function(resolve, reject) {
+                                       const successHandler = (response) => {
+                                               const status = response.data.status;
+                                               const state = status === "COMPLETED" ? SUCCESS : status === "FAILED" ? FAILED : PENDING;
+                                               state.progress = state.pending ? operation.progress + ((100 - operation.progress) / 2) : 100;
+                                               let newOp = Object.assign({}, operation, state);
+                                               resolve(newOp);
+                                       };
+                                       const failHandler = (response) => {
+                                               reject(Object.assign({}, operation, FAILED));
+                                       };
+                                       const path = getComposerApiPath('package-manager/jobs/' + operation.transactionId);
+                                       ajaxFetch(path, operation, successHandler, failHandler);
+                               });
+                       },
+                       success: CatalogPackageManagerActions.updateStatus,
+                       error: CatalogPackageManagerActions.updateStatus
+               }
+       },
+
        requestCatalogPackageUploadStatus: function () {
                return {
                        remote: function (state, upload) {
                                const transactionId = upload.transactionId;
                                return new Promise(function (resolve, reject) {
                                        const action = upload.riftAction === 'onboard' ? 'upload' : 'update';
-                                       const path = '/api/' + action + '/' + transactionId + '/state';
-                                       ajaxRequest(path, upload, resolve, reject);
+                                       const path = getStateApiPath(action, transactionId);
+                                       ajaxFetch(path, upload, resolve, reject);
                                });
                        },
                        success: CatalogPackageManagerActions.uploadCatalogPackageStatusUpdated,
index ea57627..06d1342 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,9 @@
  */
 'use strict';
 
-import _ from 'lodash'
+import _pick from 'lodash/pick'
+import _isEqual from 'lodash/isEqual'
+import _cloneDeep from 'lodash/cloneDeep'
 import cc from 'change-case'
 import alt from '../alt'
 import UID from '../libraries/UniqueId'
@@ -34,7 +36,6 @@ import ComposerAppActions from '../actions/ComposerAppActions'
 import CatalogDataSource from '../sources/CatalogDataSource'
 import ComposerAppStore from '../stores/ComposerAppStore'
 import SelectionManager from '../libraries/SelectionManager'
-import ExportSelectorDialog from '../components/ExportSelectorDialog'
 
 const defaults = {
        catalogs: [],
@@ -44,11 +45,23 @@ const defaults = {
 
 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 aMetaData = _pick(a, metaProps);
+       const bMetaData = _pick(b, metaProps);
+       return _isEqual(aMetaData, bMetaData);
 };
 
+function createItem (type) {
+       let newItem = DescriptorModelMetaFactory.createModelInstanceForType(type);
+       if (newItem){
+               newItem.id = guid();
+               UID.assignUniqueId(newItem.uiState);
+               newItem.uiState.isNew = true;
+               newItem.uiState.modified = true;
+               newItem.uiState['instance-ref-count'] = 0;
+       }
+       return newItem;
+}
+
 class CatalogDataStore {
 
        constructor() {
@@ -133,16 +146,13 @@ class CatalogDataStore {
        }
 
        addNewItemToCatalog(newItem) {
-               const id = guid();
                const type = newItem.uiState.type;
-               newItem.id = id;
-               UID.assignUniqueId(newItem.uiState);
                this.getCatalogs().filter(d => d.type === type).forEach(catalog => {
                        catalog.descriptors.push(newItem);
                });
                // update indexes and integrate new model into catalog
                this.updateCatalogIndexes(this.getCatalogs());
-               return this.getCatalogItemById(id);
+               return this.getCatalogItemById(newItem.id);
        }
 
        updateCatalogIndexes(catalogs) {
@@ -192,7 +202,7 @@ class CatalogDataStore {
                                                        vnfd.uiState['instance-ref-count'] = instanceRefCount;
                                                }
                                                // create an instance of this vnfd to carry transient ui state properties
-                                               const instance = _.cloneDeep(vnfd);
+                                               const instance = _cloneDeep(vnfd);
                                                instance.uiState['member-vnf-index'] = d['member-vnf-index'];
                                                instance['vnf-configuration'] = d['vnf-configuration'];
                                                instance['start-by-default'] = d['start-by-default'];
@@ -314,7 +324,7 @@ class CatalogDataStore {
                                                                ComposerAppActions.showError.defer({
                                                                        errorMessage: 'Cannot edit NSD/VNFD with references to instantiated Network Services'
                                                                });
-                                                               return _.cloneDeep(d);
+                                                               return _cloneDeep(d);
                                                        } else {
                                                                item.uiState.modified = modified;
                                                                requiresSave = true;
@@ -348,7 +358,7 @@ class CatalogDataStore {
                                                        ComposerAppActions.showError.defer({
                                                                errorMessage: 'Cannot edit NSD/VNFD with references to instantiated Network Services'
                                                        });
-                                                       return _.cloneDeep(d);
+                                                       return _cloneDeep(d);
                                                } else {
                                                        itemDescriptor.model.uiState.modified = true;
                                                        this.addSnapshot(itemDescriptor.model);
@@ -445,40 +455,15 @@ class CatalogDataStore {
        }
 
        createCatalogItem(type = 'nsd') {
-               const model = DescriptorModelMetaFactory.createModelInstanceForType(type);
-               if (model) {
-                       const newItem = this.addNewItemToCatalog(model);
-                       newItem.uiState.isNew = true;
-                       newItem.uiState.modified = true;
-                       newItem.uiState['instance-ref-count'] = 0;
-                       // open the new model for editing in the canvas/details panels
-                       setTimeout(() => {
-                               this.selectCatalogItem(newItem);
-                               CatalogItemsActions.editCatalogItem.defer(newItem);
-                       }, 200);
-               }
+               const newItem = createItem(type);
+               this.saveItem(newItem)
        }
 
        duplicateSelectedCatalogItem() {
-               const item = this.getFirstSelectedCatalogItem();
-               if (item) {
-                       const newItem = _.cloneDeep(item);
-                       newItem.name = newItem.name + ' Copy';
-                       const nsd = this.addNewItemToCatalog(newItem);
-                       this.selectCatalogItem(nsd);
-                       nsd.uiState.isNew = true;
-                       nsd.uiState.modified = true;
-                       nsd.uiState['instance-ref-count'] = 0;
-                       // note duplicated items get a new id, map the layout position
-                       // of the old id to the new id in order to preserve the layout
-                       if (nsd.uiState.containerPositionMap) {
-                               nsd.uiState.containerPositionMap[nsd.id] = nsd.uiState.containerPositionMap[item.id];
-                               delete nsd.uiState.containerPositionMap[item.id];
-                       }
-                       setTimeout(() => {
-                               this.selectCatalogItem(nsd);
-                               CatalogItemsActions.editCatalogItem.defer(nsd);
-                       }, 200);
+               // make request to backend to duplicate an item
+               const srcItem = this.getFirstSelectedCatalogItem();
+               if (srcItem) {
+                       CatalogPackageManagerActions.copyCatalogPackage.defer(srcItem);
                }
        }
 
@@ -537,7 +522,13 @@ class CatalogDataStore {
        saveCatalogItem() {
                const activeItem = ComposerAppStore.getState().item;
                if (activeItem) {
-                       if (activeItem.uiState['instance-ref-count'] > 0) {
+                       this.saveItem(activeItem);
+               }
+       }
+
+       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'
@@ -545,84 +536,51 @@ class CatalogDataStore {
                                return;
                        }
                        const success = () => {
-                               delete activeItem.uiState.isNew;
-                               delete activeItem.uiState.modified;
-                               this.updateCatalogItem(activeItem);
+                               delete item.uiState.modified;
+                               if (item.uiState.isNew) {
+                                       this.addNewItemToCatalog(item);
+                                       delete item.uiState.isNew;
+                               } else {
+                                       this.updateCatalogItem(item);
+                               }
                                // TODO should the save action clear the undo/redo stack back to the beginning?
-                               this.resetSnapshots(activeItem);
+                               this.resetSnapshots(item);
                                ModalOverlayActions.hideModalOverlay.defer();
-                               CatalogItemsActions.editCatalogItem.defer(activeItem);
+                               CatalogItemsActions.editCatalogItem.defer(item);
                        };
                        const failure = () => {
                                ModalOverlayActions.hideModalOverlay.defer();
-                               CatalogItemsActions.editCatalogItem.defer(activeItem);
+                               CatalogItemsActions.editCatalogItem.defer(item);
                        };
                        const exception = () => {
-                               console.warn('unable to save catalog item', activeItem);
+                               console.warn('unable to save catalog item', item);
                                ModalOverlayActions.hideModalOverlay.defer();
-                               CatalogItemsActions.editCatalogItem.defer(activeItem);
+                               CatalogItemsActions.editCatalogItem.defer(item);
                        };
                        ModalOverlayActions.showModalOverlay.defer();
-                       this.getInstance().saveCatalogItem(activeItem).then(success, failure).catch(exception);
+                       this.getInstance().saveCatalogItem(item).then(success, failure).catch(exception);
                }
        }
 
        exportSelectedCatalogItems(draggedItem) {
-               const onSelectFormat = (selectedFormat, event) => {
-                       this.setState({
-                               selectedFormat: selectedFormat
-                       });
-               };
-
-               const onSelectGrammar = (selectedGrammar, event) => {
-                       this.setState({
-                               selectedGrammar: selectedGrammar
-                       });
-               }
-
-
-               const onCancel = () => {
-                       this.resetSelectionState();
-                       ModalOverlayActions.hideModalOverlay();
-               };
-
-               const onDownload = (event) => {
+               // collect the selected items and delegate to the catalog package manager action creator
+               const selectedItems = this.getAllSelectedCatalogItems();
+               if (selectedItems.length) {
                        CatalogPackageManagerActions.downloadCatalogPackage.defer({
                                selectedItems: selectedItems,
-                               selectedFormat: this.selectedFormat,
-                               selectedGrammar: this.selectedGrammar
+                               selectedFormat: 'mano',
+                               selectedGrammar: 'osm'
                        });
                        this.resetSelectionState();
-                       ModalOverlayActions.hideModalOverlay();
-                       return;
-               }
-
-               if (draggedItem) {
-                       // if item is given make sure it is also selected
-                       //draggedItem.uiState.selected = true;
-                       SelectionManager.addSelection(draggedItem);
-                       this.updateCatalogItem(draggedItem);
-               }
-               // collect the selected items and delegate to the catalog package manager action creator
-               const selectedItems = this.getAllSelectedCatalogItems();
-               if (selectedItems.length) {
-                       CatalogDataStore.chooseExportFormat(onSelectFormat, onSelectGrammar, onDownload, onCancel);
                }
        }
-
-       static chooseExportFormat(onSelectFormat, onSelectGrammar, onDownload, onCancel) {
-               ModalOverlayActions.showModalOverlay.defer(
-                       <ExportSelectorDialog
-                               onSelectFormat={onSelectFormat}
-                               onSelectGrammar={onSelectGrammar}
-                               onCancel={onCancel}
-                               onDownload={onDownload}
-                               currentlySelectedFormat='mano'
-                               currentlySelectedGrammar='osm'
-                       />
-               );
+       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, ' ')
+               ComposerAppActions.showError.defer({
+                       errorMessage: 'Unable to save the descriptor.\n' + errorMsg
+               });
        }
-
 }
 
 export default alt.createStore(CatalogDataStore, 'CatalogDataStore');
index 3a82114..9d23042 100644 (file)
@@ -18,7 +18,8 @@
  */
 'use strict';
 
-import _ from 'lodash'
+import _delay from 'lodash/delay'
+import _pick from 'lodash/pick'
 import alt from '../alt'
 import guid from '../libraries/guid'
 import numeral from 'numeral'
@@ -31,8 +32,21 @@ import CatalogDataSource from '../sources/CatalogDataSource'
 import imgDownload from '../../../node_modules/open-iconic/svg/cloud-download.svg'
 import imgOnboard from '../../../node_modules/open-iconic/svg/cloud-upload.svg'
 import imgUpdate from '../../../node_modules/open-iconic/svg/data-transfer-upload.svg'
+import imgCopy from '../../../node_modules/open-iconic/svg/layers.svg'
 
 const defaults = {
+       operation: {
+               id: '',
+               name: '',
+               icon: '',
+               transactionId: '',
+               progress: 0,
+               message: 'Requested',
+               args: {},
+               pending: false,
+               success: false,
+               error: false,
+       },
        downloadPackage: {
                id: '',
                name: '',
@@ -59,13 +73,13 @@ function getCatalogPackageManagerServerOrigin() {
        return utils.getSearchParams(window.location).upload_server + ':4567';
 }
 
-function delayStatusCheck(statusCheckFunction, catalogPackage) {
-       if (!catalogPackage.checkStatusTimeoutId) {
+function delayStatusCheck(statusCheckFunction, operation) {
+       if (!operation.checkStatusTimeoutId) {
                const delayCallback = function () {
-                       delete catalogPackage.checkStatusTimeoutId;
-                       statusCheckFunction(catalogPackage).catch(exception);
+                       delete operation.checkStatusTimeoutId;
+                       statusCheckFunction(operation).catch(exception);
                };
-               catalogPackage.checkStatusTimeoutId = _.delay(delayCallback, defaults.checkStatusDelayInSeconds * 1000);
+               operation.checkStatusTimeoutId = _delay(delayCallback, defaults.checkStatusDelayInSeconds * 1000);
        }
 }
 
@@ -73,52 +87,78 @@ class CatalogPackageManagerStore {
 
        constructor() {
 
-               this.packages = [];
+               this.operations = [];
 
                this.registerAsync(CatalogDataSource);
                this.registerAsync(CatalogPackageManagerSource);
-               this.bindAction(CatalogPackageManagerActions.REMOVE_CATALOG_PACKAGE, this.removeCatalogPackage);
+               this.bindAction(CatalogPackageManagerActions.REMOVE_CATALOG_OPERATION, this.removeCatalogOperation);
                this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE, this.downloadCatalogPackage);
                this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onDownloadCatalogPackageStatusUpdated);
                this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_ERROR, this.onDownloadCatalogPackageError);
                this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE, this.uploadCatalogPackage);
                this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onUploadCatalogPackageStatusUpdated);
                this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_ERROR, this.onUploadCatalogPackageError);
-
+               this.bindAction(CatalogPackageManagerActions.COPY_CATALOG_PACKAGE, this.copyCatalogPackage);
+               this.bindAction(CatalogPackageManagerActions.UPDATE_STATUS, this.updateOperationStatus);
        }
 
-       addPackage(catalogPackage) {
-               const packages = [catalogPackage].concat(this.packages);
-               this.setState({packages: packages});
+       addOperation(operation) {
+               const operations = [operation].concat(this.operations);
+               this.setState({operations});
        }
 
-       updatePackage(catalogPackage) {
-               const packages = this.packages.map(d => {
-                       if (d.id === catalogPackage.id) {
-                               return Object.assign({}, d, catalogPackage);
+       updateOperation(operation) {
+               const operations = this.operations.map(d => {
+                       if (d.id === operation.id) {
+                               return Object.assign({}, d, operation);
                        }
                        return d;
                });
-               this.setState({packages: packages});
+               this.setState({operations});
        }
 
-       removeCatalogPackage(catalogPackage) {
-               const packages = this.packages.filter(d => d.id !== catalogPackage.id);
-               this.setState({packages: packages});
+       removeCatalogOperation(operation) {
+               const operations = this.operations.filter(d => d.id !== operation.id);
+               this.setState({operations});
+       }
+
+       copyCatalogPackage(sourcePackage) {
+               let operationInfo = Object.assign({}, defaults.operation);
+
+               operationInfo.args.packageType = sourcePackage['uiState']['type'].toUpperCase();
+               operationInfo.args.id =  sourcePackage.id;
+               operationInfo.args.name =  sourcePackage.name + ' copy';
+
+               operationInfo.id = guid();
+               operationInfo.icon = imgCopy;
+               operationInfo.type = 'copy';
+               operationInfo.name =  "Creating " + operationInfo.args.name;
+               operationInfo.message = "Requesting duplication";
+
+               this.addOperation(operationInfo);
+               this.getInstance().requestCatalogPackageCopy(operationInfo, sourcePackage);
+       }
+
+       updateOperationStatus(operation) {
+               console.debug('package manager operation status update', operation);
+               this.updateOperation(operation);
+               if (operation.pending) {
+                       delayStatusCheck(this.getInstance().requestCatalogPackageCopyStatus, operation);
+               }
        }
 
        uploadCatalogPackage(file) {
                file.id = file.id || guid();
-               const catalogPackage = _.pick(file, packagePropertyNames);
-               catalogPackage.icon = file.riftAction === 'onboard' ? imgOnboard : imgUpdate;
-               catalogPackage.type = 'upload';
-               this.addPackage(catalogPackage);
+               const operation = _pick(file, packagePropertyNames);
+               operation.icon = file.riftAction === 'onboard' ? imgOnboard : imgUpdate;
+               operation.type = 'upload';
+               this.addOperation(operation);
                // note DropZone.js handles the async upload so we don't have to invoke any async action creators
        }
 
        onUploadCatalogPackageStatusUpdated(response) {
                const upload = updateStatusInfo(response);
-               this.updatePackage(upload);
+               this.updateOperation(upload);
                console.log('updating package upload')
                // if pending with no transaction id - do nothing
                // bc DropZone.js will notify upload progress
@@ -133,8 +173,8 @@ class CatalogPackageManagerStore {
 
        onUploadCatalogPackageError(response) {
                console.warn('onUploadCatalogPackageError', response);
-               const catalogPackage = updateStatusInfo(response);
-               this.updatePackage(catalogPackage);
+               const operation = updateStatusInfo(response);
+               this.updateOperation(operation);
        }
 
        downloadCatalogPackage(data) {
@@ -143,22 +183,22 @@ class CatalogPackageManagerStore {
                let grammar = data['selectedGrammar'] || 'osm';
                let format = "YAML";
                if (catalogItems.length) {
-                       const catalogPackage = Object.assign({}, defaults.downloadPackage, {id: guid()});
-                       catalogPackage.name = catalogItems[0].name;
-                       catalogPackage.type = 'download';
+                       const operation = Object.assign({}, defaults.downloadPackage, {id: guid()});
+                       operation.name = catalogItems[0].name;
+                       operation.type = 'download';
                        if (catalogItems.length > 1) {
-                               catalogPackage.name += ' (' + catalogItems.length + ' items)';
+                               operation.name += ' (' + catalogItems.length + ' items)';
                        }
-                       catalogPackage.ids = catalogItems.map(d => d.id).sort().toString();
-                       catalogPackage.catalogItems = catalogItems;
-                       this.addPackage(catalogPackage);
-                       this.getInstance().requestCatalogPackageDownload(catalogPackage, format, grammar, schema).catch(exception);
+                       operation.ids = catalogItems.map(d => d.id).sort().toString();
+                       operation.catalogItems = catalogItems;
+                       this.addOperation(operation);
+                       this.getInstance().requestCatalogPackageDownload(operation, format, grammar, schema).catch(exception);
                }
        }
 
        onDownloadCatalogPackageStatusUpdated(response) {
                const download = updateStatusInfo(response);
-               this.updatePackage(download);
+               this.updateOperation(download);
                if (download.pending) {
                        delayStatusCheck(this.getInstance().requestCatalogPackageDownloadStatus, download);
                }
@@ -166,8 +206,8 @@ class CatalogPackageManagerStore {
 
        onDownloadCatalogPackageError(response) {
                console.warn('onDownloadCatalogPackageError', response);
-               const catalogPackage = updateStatusInfo(response);
-               this.updatePackage(catalogPackage);
+               const operation = updateStatusInfo(response);
+               this.updateOperation(operation);
        }
 
 }
@@ -187,61 +227,67 @@ function calculateUploadProgressMessage(size = 0, progress = 0, bytesSent = 0) {
 }
 
 function updateStatusInfo(response) {
-       // returns the catalogPackage object with the status fields updated based on the server response
+       // returns the operation object with the status fields updated based on the server response
        const statusInfo = {
                pending: false,
                success: false,
                error: false
        };
        const responseData = (response.data.output) ? response.data.output :  response.data;
-       const catalogPackage = response.state;
-       switch(response.data.status) {
-       case 'upload-progress':
-               statusInfo.pending = true;
-               statusInfo.progress = parseFloat(responseData.progress) || 0;
-               statusInfo.message = calculateUploadProgressMessage(catalogPackage.size, responseData.progress, responseData.bytesSent);
-               break;
-       case 'upload-success':
-               statusInfo.pending = true;
-               statusInfo.progress = 100;
-               statusInfo.message = 'Upload completed.';
-               statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || catalogPackage.transactionId;
-               break;
-       case 'upload-error':
-               statusInfo.error = true;
-               statusInfo.message = responseData.message;
-               break;
-       case 'download-requested':
-               statusInfo.pending = true;
-               statusInfo.progress = 25;
-               statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id']  || catalogPackage.transactionId;
-               break;
-       case 'pending':
-               statusInfo.pending = true;
-               statusInfo.progress = 50;
-               statusInfo.message = responseData.events[responseData.events.length - 1].text;
-               break;
-       case 'success':
-               statusInfo.success = true;
-               statusInfo.progress = 100;
-               statusInfo.message = responseData.events[responseData.events.length - 1].text;
-               if (catalogPackage.type === 'download') {
-                       statusInfo.urlValidUntil = moment().add(defaults.downloadUrlTimeToLiveInMinutes, 'minutes').toISOString();
-                       if (responseData.filename) {
-                               statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + responseData.filename;
-                       } else {
-                               statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + catalogPackage.transactionId + '.tar.gz';
+       const operation = response.state;
+       if ( typeof response.data.status !== "number" ) {
+               switch(response.data.status) {
+               case 'upload-progress':
+                       statusInfo.pending = true;
+                       statusInfo.progress = parseFloat(responseData.progress) || 0;
+                       statusInfo.message = calculateUploadProgressMessage(operation.size, responseData.progress, responseData.bytesSent);
+                       break;
+               case 'upload-success':
+                       statusInfo.pending = true;
+                       statusInfo.progress = 100;
+                       statusInfo.message = 'Upload completed.';
+                       statusInfo.transactionId = responseData['transaction_id'] || responseData['transaction-id'] || operation.transactionId;
+                       break;
+               case 'upload-error':
+                       statusInfo.error = true;
+                       statusInfo.message = responseData.message;
+                       break;
+               case 'download-requested':
+                       statusInfo.pending = true;
+                       statusInfo.progress = 25;
+                       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;
+                       break;
+               case 'success':
+                       statusInfo.success = true;
+                       statusInfo.progress = 100;
+                       statusInfo.message = responseData.events[responseData.events.length - 1].text;
+                       if (operation.type === 'download') {
+                               statusInfo.urlValidUntil = moment().add(defaults.downloadUrlTimeToLiveInMinutes, 'minutes').toISOString();
+                               if (responseData.filename) {
+                                       statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + responseData.filename;
+                               } else {
+                                       statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + operation.transactionId + '.tar.gz';
+                               }
                        }
+                       break;
+               case 'failure':
+                       statusInfo.error = true;
+                       statusInfo.message = responseData.errors[0].value;
+                       break;
+               default:
+                       throw new ReferenceError('a status of "request", "success", "failure", "pending", "upload-completed", "upload-error", "download-requested", "upload-progress", "upload-action" is required');
                }
-               break;
-       case 'failure':
+       } else {
+               // typically get here due to unexpected development errors (backend exceptions, down/up load server access issues)
                statusInfo.error = true;
-               statusInfo.message = responseData.errors[0].value;
-               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.message = responseData.statusText || 'Error';
        }
-       return Object.assign({}, catalogPackage, statusInfo);
+       return Object.assign({}, operation, statusInfo);
 }
 
 export default alt.createStore(CatalogPackageManagerStore, 'CatalogPackageManagerStore');
index ff24cbb..c677a44 100644 (file)
  */
 'use strict';
 
-import _ from 'lodash'
+import _isNumber from 'lodash/isNumber'
+import _cloneDeep from 'lodash/cloneDeep'
+import _isEmpty from 'lodash/isEmpty'
+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 d3 from 'd3'
 import alt from '../alt'
 import UID from '../libraries/UniqueId'
@@ -56,7 +63,7 @@ class ComponentBridge extends React.Component {
 const getDefault = (name, defaultValue) => {
        const val = window.localStorage.getItem('defaults-' + name);
        if (val) {
-               if (_.isNumber(val)) {
+               if (_isNumber(val)) {
                        if (val < 0) {
                                return setDefault(name, 0);
                        }
@@ -165,6 +172,7 @@ class ComposerAppStore {
                        addFileSuccess: FileManagerActions.addFileSuccess,
                        deletePackageFile: FileManagerActions.deletePackageFile,
                        deleteFileSuccess: FileManagerActions.deleteFileSuccess,
+                       deleteFileError: FileManagerActions.deleteFileError,
                        closeFileManagerSockets: FileManagerActions.closeFileManagerSockets,
                        openFileManagerSockets: FileManagerActions.openFileManagerSockets,
                        openDownloadMonitoringSocketSuccess: FileManagerActions.openDownloadMonitoringSocketSuccess,
@@ -218,7 +226,7 @@ class ComposerAppStore {
                                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();
        }
@@ -234,7 +242,9 @@ class ComposerAppStore {
                }
                SelectionManager.select(item);
                this.updateItem(item);
-               this.openFileManagerSockets(item)
+               if (item) {
+                       this.openFileManagerSockets(item);
+               }
        }
        catalogItemMetaDataChanged(item) {
                this.updateItem(item);
@@ -267,7 +277,7 @@ class ComposerAppStore {
 
        applyDefaultLayout() {
                if (this.item && this.item.uiState && this.item.uiState.containerPositionMap) {
-                       if (!_.isEmpty(this.item.uiState.containerPositionMap)) {
+                       if (!_isEmpty(this.item.uiState.containerPositionMap)) {
                                this.item.uiState.containerPositionMap = {};
                                CatalogItemsActions.catalogItemMetaDataChanged.defer(this.item);
                        }
@@ -410,7 +420,7 @@ class ComposerAppStore {
 
                        if (isFullScreen()) {
                                const layout = comp.layout;
-                               const restoreLayout = _.cloneDeep(layout);
+                               const restoreLayout = _cloneDeep(layout);
                                uiTransientState.restoreLayout = restoreLayout;
                                layout.left = 0;
                                layout.right = 0;
@@ -475,13 +485,12 @@ class ComposerAppStore {
         if (self.fileMonitoringSocketID) {
                let newState = {};
                if(data.hasOwnProperty('contents')) {
-                       filesState = addInputState( _.cloneDeep(this.filesState),data);
-                               // filesState = _.merge(self.filesState, addInputState({},data));
+                       filesState = addInputState( _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
                                        },
@@ -492,8 +501,10 @@ class ComposerAppStore {
                                files: false
                        }
                }
+               if(!_isEqual(newState.files, this.files) || ! _isEqual(newState.fileState, this.fileState)) {
+                       this.setState(newState);
+               }
 
-                       this.setState(newState);
         }
                function normalizeTree(data) {
                        let f = {
@@ -538,7 +549,7 @@ class ComposerAppStore {
        updateFileLocationInput = (data) => {
                let name = data.name;
                let value = data.value;
-               var filesState = _.cloneDeep(this.filesState);
+               var filesState = _cloneDeep(this.filesState);
                filesState[name] = value;
                this.setState({
                        filesState: filesState
@@ -548,7 +559,7 @@ class ComposerAppStore {
                if(!data.refresh) {
                        let path = data.path;
                        let fileName = data.fileName;
-                       let files = _.cloneDeep(this.files);
+                       let files = _cloneDeep(this.files);
                        let loadingIndex = files.data[path].push({
                                status: 'DOWNLOADING',
                                name: path + '/' + fileName
@@ -566,7 +577,7 @@ class ComposerAppStore {
        openDownloadMonitoringSocketSuccess = (id) => {
                let self = this;
                let ws = window.multiplexer.channel(id);
-               let downloadJobs = _.cloneDeep(self.downloadJobs);
+               let downloadJobs = _cloneDeep(self.downloadJobs);
                let newFiles = false;
                ws.onmessage = (socket) => {
             if (self.files && self.files.length > 0) {
@@ -574,14 +585,14 @@ class ComposerAppStore {
                 try {
                     jobs = JSON.parse(socket.data);
                 } catch(e) {}
-                newFiles = _.cloneDeep(self.files);
+                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){
+                    let index = _findIndex(self.files.data[path], function(o){
                         return fullPath == o.name
                     });
                     if((index > -1) && newFiles.data[path][index]) {
@@ -659,11 +670,11 @@ class ComposerAppStore {
        }
        deleteFileSuccess = (data) => {
                let path = data.path.split('/')
-               let files = _.cloneDeep(this.files);
+               let files = _cloneDeep(this.files);
                path.pop();
                path = path.join('/');
                let pathFiles = files.data[path]
-               _.remove(pathFiles, function(c) {
+               _remove(pathFiles, function(c) {
                        return c.name == data.path;
                });
 
@@ -671,6 +682,15 @@ class ComposerAppStore {
                        files: files
                })
        }
+       deleteFileError = (error) => {
+               const filepath = error.path;
+               const message = error.data && error.data.output ? ' (' + error.data.output['error-trace'] + ')' : ' (server error)';
+               console.log('Unable to delete', filepath, 'Error:', message);
+               ComposerAppActions.showError.defer({
+                       errorMessage: 'Unable to delete ' + filepath + message + '. '
+               });
+       }
+
        newPathNameUpdated = (event) => {
                const value = event.target.value;
                this.setState({
index 66376b9..eff8962 100644 (file)
@@ -21,7 +21,7 @@
 /*global describe, beforeEach, it, expect, xit, xdescribe */
 
 'use strict';
-import _ from 'lodash'
+import _cloneDeep from 'lodash/cloneDeep'
 import DescriptorModelSerializer from '../../../src/libraries/model/DescriptorModelSerializer'
 import DescriptorModelFactory from '../../../src/libraries/model/DescriptorModelFactory'
 import SampleCatalogs from 'json!../../../src/assets/ping-pong-catalog.json'
@@ -34,7 +34,7 @@ describe('DescriptorModelFactory', () => {
        describe('buildCatalogItemFactory', () => {
                let containers;
                beforeEach(() => {
-                       const nsdJson = _.cloneDeep(SampleCatalogs[0].descriptors[0]);
+                       const nsdJson = _cloneDeep(SampleCatalogs[0].descriptors[0]);
                        // the CatalogItemsStore adds the type to the uiState field when the catalog is loaded
                        nsdJson.uiState = {type: 'nsd'};
                        // the user will open a catalog item by dbl clicking on it in the ui that is when we
@@ -49,7 +49,7 @@ describe('DescriptorModelFactory', () => {
                        expect(result).toEqual([]);
                });
                it('parses an NSD object', () => {
-                       const nsdJson = _.cloneDeep(SampleCatalogs[0].descriptors[0]);
+                       const nsdJson = _cloneDeep(SampleCatalogs[0].descriptors[0]);
                        nsdJson.uiState = {type: 'nsd'};
                        const factory = DescriptorModelFactory.buildCatalogItemFactory(SampleCatalogs);
                        const result = [nsdJson].reduce(factory, [])[0];
@@ -68,7 +68,7 @@ describe('DescriptorModelFactory', () => {
                });
                describe('DescriptorModelSerializer', () => {
                        it('outputs the same JSON that was parsed by the .buildCatalogItemFactory method', () => {
-                               const inputJSON = _.cloneDeep(TestCatalogs[0].descriptors[0]);
+                               const inputJSON = _cloneDeep(TestCatalogs[0].descriptors[0]);
                                inputJSON.uiState = {type: 'nsd'};
                                const factory = DescriptorModelFactory.buildCatalogItemFactory(TestCatalogs);
                                const parsedModel = [inputJSON].reduce(factory, []);
index 5388780..b65e2e7 100644 (file)
@@ -21,7 +21,6 @@
 /*global describe, beforeEach, it, expect, xit */
 
 'use strict';
-import _ from 'lodash'
 import DescriptorModel from 'libraries/model/DescriptorModel'
 
 class TestDescriptorModel extends DescriptorModel {
index bf2747a..7839f10 100644 (file)
@@ -28,14 +28,15 @@ var path = require('path');
 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;
-module.exports = {
+var config = {
        devtool: 'source-map',
        output: {
                publicPath: 'assets/',
                path: 'public/assets/',
-               filename: 'src/main.js'
+               filename: 'bundle.js'
        },
 
        debug: false,
@@ -60,18 +61,6 @@ module.exports = {
                        'helpers': path.join(process.cwd(), './test/helpers/')
         }
     },
-       plugins: [
-               // new webpack.optimize.DedupePlugin(),
-               // new webpack.optimize.UglifyJsPlugin(),
-               // new webpack.optimize.OccurenceOrderPlugin(),
-               // new webpack.optimize.AggressiveMergingPlugin(),
-               // new webpack.NoErrorsPlugin(),
-               new HtmlWebpackPlugin({
-            filename: '../index.html'
-            , templateContent: '<div id="app"></div>'
-        })
-       ],
-
        module: {
                noParse: [/autoit.js/],
                // preLoaders: [
@@ -101,5 +90,28 @@ module.exports = {
                        },
                        { test: /\.json$/, loader: "json-loader" },
                ]
-       }
+       },
+    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;
\ No newline at end of file
index b4de560..ff3212a 100644 (file)
@@ -38,6 +38,7 @@
     "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",
index 1758212..4a5f8c6 100755 (executable)
@@ -13,6 +13,6 @@ echo 'Fetching third-party node_modules for '$PLUGIN_NAME
 npm install
 echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
 echo 'Packaging '$PLUGIN_NAME' using webpack'
-ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+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'
index 457a08e..1636f4c 100644 (file)
@@ -4,7 +4,7 @@
 
 import React from 'react';
 import Button from 'widgets/button/rw.button.js';
-import _ from 'lodash';
+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';
@@ -79,7 +79,7 @@ class Account extends React.Component {
             }
         }
 
-        let newAccount = _.cloneDeep(removeTrailingWhitespace(Account));
+        let newAccount = _cloneDeep(removeTrailingWhitespace(Account));
         delete newAccount.params;
         newAccount.nestedParams &&
             newAccount.nestedParams['container-name'] &&
index 49ad631..f78ff42 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * STANDARD_RIFT_IO_COPYRIGHT
  */
-var Webpack = require('webpack');
+var webpack = require('webpack');
 var path = require('path');
 var nodeModulesPath = path.resolve(__dirname, 'node_modules');
 var buildPath = path.resolve(__dirname, 'public', 'build');
@@ -9,7 +9,7 @@ var mainPath = path.resolve(__dirname, 'src', 'main.js');
 var uiPluginCmakeBuild = process.env.ui_plugin_cmake_build || false;
 var frameworkPath = uiPluginCmakeBuild?'../../../../skyquake/skyquake-build/framework':'../../framework';
 var HtmlWebpackPlugin = require('html-webpack-plugin');
-var CommonsPlugin = new require("webpack/lib/optimize/CommonsChunkPlugin")
+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 = {
@@ -52,10 +52,24 @@ var config = {
     },
     plugins: [
         new HtmlWebpackPlugin({
-            filename: '../index.html'
-            , templateContent: '<div id="app"></div>'
-        }),
-        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+            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;
index fd2dea9..2395b14 100644 (file)
@@ -35,6 +35,7 @@
     "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",
index 49bc9f0..7bac1bf 100755 (executable)
@@ -27,6 +27,6 @@ echo 'Fetching third-party node_modules for '$PLUGIN_NAME
 npm install
 echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
 echo 'Packaging '$PLUGIN_NAME' using webpack'
-ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+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'
index 6c659c3..bed1dbf 100644 (file)
@@ -21,114 +21,126 @@ import './crash.scss';
 import TreeView from 'react-treeview';
 import '../node_modules/react-treeview/react-treeview.css';
 import AppHeader from 'widgets/header/header.jsx';
+import ScreenLoader from 'widgets/screen-loader/screenLoader.jsx';
 var crashActions = require('./crashActions.js');
 var crashStore = require('./crashStore.js');
 // var MissionControlStore = require('../missioncontrol/missionControlStore.js');
 function openDashboard() {
-  window.location.hash = "#/";
+    window.location.hash = "#/";
 }
 class CrashDetails extends React.Component {
-  constructor(props) {
-    super(props)
-    var self = this;
-    this.state = crashStore.getState();
-    crashStore.listen(this.storeListener);
-  }
-  storeListener = (data) => {
-     this.setState({
-        list:data.crashList,
-        noDebug:!this.hasDebugData(data.crashList)
-      });
-  }
-  componentWillUnmount(){
-    crashStore.unlisten(this.storeListener);
-  }
-  componentWillMount() {
-    crashStore.get();
-  }
-  hasDebugData(list) {
-    console.log(list);
-    if (list && list.length > 0) {
-      for (let i = 0; i < list.length; i++) {
-        var trace = list[i].backtrace;
-        for (let j = 0; j < trace.length; j++) {
-          console.log(trace[j])
-          if (trace[j].detail) {
-            return true;
-          }
+    constructor(props) {
+        super(props)
+        this.state = crashStore.getState();
+        crashStore.listen(this.storeListener);
+    }
+    storeListener = (data) => {
+        this.setState({
+            isLoading: data.isLoading,
+            list: data.crashList,
+            noDebug: !this.hasDebugData(data.crashList)
+        });
+    }
+    componentWillUnmount() {
+        crashStore.unlisten(this.storeListener);
+    }
+    componentWillMount() {
+        crashStore.get();
+    }
+    hasDebugData(list) {
+        console.log(list);
+        if (list && list.length > 0) {
+            for (let i = 0; i < list.length; i++) {
+                var trace = list[i].backtrace;
+                for (let j = 0; j < trace.length; j++) {
+                    console.log(trace[j])
+                    if (trace[j].detail) {
+                        return true;
+                    }
+                }
+            }
         }
-      }
+        return false;
     }
-    return false;
-  }
-  downloadFile(fileName, urlData) {
-    var replacedNewLines = urlData.replace(/\\n/g, '\n');
-    var replacedTabs = replacedNewLines.replace(/\\t/g, '\t');
-    var replacedQuotes= replacedTabs.replace(/\\"/g, '"');
-    var textFileBlob = new Blob([replacedQuotes], {type: 'text/plain;charset=UTF-8'});
-    var aLink = document.createElement('a');
-    var evt = document.createEvent("HTMLEvents");
-    evt.initEvent("click");
-    aLink.download = fileName;
-    aLink.href = window.URL.createObjectURL(textFileBlob);
-    aLink.dispatchEvent(evt);
-  }
-  render() {
-    let html;
-    var list = null;
-    if (this.state != null) {
-      var tree = <div style={{'marginLeft':'auto', 'marginRight':'auto', 'width':'230px', 'padding':'90px'}}> No Debug Information Available </div>;
-      if (!this.state.noDebug)
-      {
-        var tree = this.state.list && this.state.list.map((node, i) => {
-                  var vm = node.name;
-                  var vm_label = <span>{vm}</span>;
-                  var backtrace = node.backtrace;
-                  return (
-                    <TreeView key={vm + '|' + i} nodeLabel={vm_label} defaultCollapsed={false}>
-                      {backtrace.map(details => {
-
-                        //Needed to decode HTML
-                        var text_temp = document.createElement("textarea")
-                        text_temp.innerHTML = details.detail;
-                        var text_temp = text_temp.value;
-                        var arr = text_temp.split(/\n/);
-                        var text = [];
-                        for (let i = 0; i < arr.length; i++) {
-                          text.push(arr[i]);
-                          text.push(<br/>)
-                        }
+    downloadFile(fileName) {
+        // wait till download selected to create text blob
+        let urlData = JSON.stringify(this.state.list, null, 2);
+        let replacedNewLines = urlData.replace(/\\n/g, '\n');
+        let replacedTabs = replacedNewLines.replace(/\\t/g, '\t');
+        let replacedQuotes = replacedTabs.replace(/\\"/g, '"');
+        let textFileBlob = new Blob([replacedQuotes], { type: 'text/plain;charset=UTF-8' });
+        let aLink = document.createElement('a');
+        aLink.download = fileName;
+        aLink.href = window.URL.createObjectURL(textFileBlob);
+        aLink.click(); // suprise, this works without being on the page
+        // it seems to cause no problems cleaning up the blob right away
+        window.URL.revokeObjectURL(aLink.href);
+        // assuming aLink just goes away when this function ends
+    }
+    render() {
+        let html;
+        if (this.state != null) {
+            if (!this.state.noDebug) {
+                let tree = this.state.list && this.state.list.map((node, i) => {
+                    const vm = node.name;
+                    const vm_label = <span>{vm}</span>;
+                    const backtrace = node.backtrace;
+                    return (
+                        <TreeView key={vm + '|' + i} nodeLabel={vm_label} defaultCollapsed={false}>
+                            {backtrace.map(details => {
+                                // do some trickery to normalize details 'new line' char(s)
+                                let textareaElement = document.createElement("textarea")
+                                textareaElement.innerHTML = details.detail;
+                                let detailsFormatted = textareaElement.value;
+                                let arr = detailsFormatted.split(/\n/);
+                                let text = [];
+                                for (let i = 0; i < arr.length; i++) {
+                                    text.push(arr[i]);
+                                    text.push(<br key={'line-' + i} />); // react likes keys on array children
+                                }
 
-                        return (
-                          <TreeView nodeLabel={<span>{details.id}</span>} key={vm + '||' + details.id} defaultCollapsed={false}>
-                            <p>{text}</p>
-                          </TreeView>
-                        );
-                      })}
-                    </TreeView>
-                  );
-                })}
-      html = (
-        <div className="crash-details-wrapper">
-              <div className="form-actions">
-                <button role="button" className="dark" onClick={this.state.noDebug ? false : this.downloadFile.bind(this, 'crash.txt', 'data:text;charset=UTF-8,' + decodeURIComponent(JSON.stringify(this.state.list, null, 2)))}> Download Crash Details</button>
-              </div>
-              <div className="crash-container">
-                <h2> Debug Information </h2>
-                <div className="tree">{tree}</div>
-              </div>
-        </div>
-              );
-    } else {
-      html = <div className="crash-container"></div>
-    };
-    let refPage = window.sessionStorage.getItem('refPage');
-    refPage = JSON.parse(refPage);
-    return (
+                                return (
+                                    <TreeView nodeLabel={<span>{details.id}</span>} key={vm + '||' + details.id} defaultCollapsed={false}>
+                                        <p>{text}</p>
+                                    </TreeView>
+                                );
+                            })}
+                        </TreeView>
+                    );
+                });
+                let doDownload = this.downloadFile.bind(this, 'crash.txt');
+                html = (
+                    <div className="crash-details-wrapper">
+                        <div className="form-actions">
+                            <button role="button" className="dark" onClick={this.state.noDebug ? false : doDownload}>Download Crash Details</button>
+                        </div>
+                        <div className="crash-container">
+                            <h2> Debug Information </h2>
+                            <div className="tree">{tree}</div>
+                        </div>
+                    </div>
+                );
+            } else {
+                let text = this.state.isLoading ? "Loading Debug Information" : "No Debug Information Available"
+                html = (
+                    <div className="crash-details-wrapper">
+                        <div className="crash-container">
+                            <div style={{ 'marginLeft': 'auto', 'marginRight': 'auto', 'width': '230px', 'padding': '90px' }}>{text}</div>
+                        </div>
+                    </div>
+                );
+            }
+        } else {
+            html = <div className="crash-container"></div>
+        };
+        let refPage = window.sessionStorage.getItem('refPage');
+        refPage = JSON.parse(refPage);
+        return (
             <div className="crash-app">
-              {html}
+                {html}
+                <ScreenLoader show={this.state.isLoading}/> 
             </div>
-            );
-  }
+        );
+    }
 }
 export default CrashDetails;
index c5ded18..0b04445 100644 (file)
@@ -20,20 +20,31 @@ import Alt from 'widgets/skyquake_container/skyquakeAltInstance';
 function crashStore () {
   this.exportAsync(require('./crashSource.js'));
   this.bindActions(require('./crashActions.js'));
+  this.isLoading = false;
+  this.crashList = null;
 }
 
 crashStore.prototype.getCrashDetailsSuccess = function(list) {
   this.setState({
-    crashList:list
+    isLoading: false,
+    crashList: list
   })
-  console.log('success', list)
+  console.log('Crash details load success', list)
 };
 crashStore.prototype.getCrashDetailsLoading = function(info) {
+  this.setState({
+    isLoading: true,
+    crashList: null,
+  })
   console.log('Loading crash details...', info)
 };
 crashStore.prototype.getCrashDetailsFailure = function(info) {
+  this.setState({
+    isLoading: false,
+    error: info
+  })
   console.log('Failed to retrieve crash/debug details', info)
 };
 
-module.exports = Alt.createStore(crashStore);;
+module.exports = Alt.createStore(crashStore, 'crashStore');;
 
index 5be840c..2ef01be 100644 (file)
@@ -15,7 +15,7 @@
  *   limitations under the License.
  *
  */
-var Webpack = require('webpack');
+var webpack = require('webpack');
 var path = require('path');
 var nodeModulesPath = path.resolve(__dirname, 'node_modules');
 var buildPath = path.resolve(__dirname, 'public', 'build');
@@ -23,6 +23,7 @@ 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 = {
@@ -65,10 +66,24 @@ var config = {
     },
     plugins: [
         new HtmlWebpackPlugin({
-            filename: '../index.html'
-            , templateContent: '<div id="app"></div>'
-        }),
-        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+            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;
index ebb138c..a6d1bba 100644 (file)
@@ -15,6 +15,7 @@
     "babel-preset-es2015": "^6.3.13",
     "babel-preset-stage-0": "^6.3.13",
     "babel-runtime": "^6.3.19",
+    "compression-webpack-plugin": "^0.3.2",
     "express": "^4.13.3",
     "history": "^1.17.0",
     "json-loader": "^0.5.4",
index 6dc7f5c..3298c2a 100755 (executable)
@@ -27,6 +27,6 @@ echo 'Fetching third-party node_modules for '$PLUGIN_NAME
 npm install
 echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
 echo 'Packaging '$PLUGIN_NAME' using webpack'
-ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+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'
index e013478..7db6acc 100644 (file)
@@ -15,7 +15,7 @@
  *   limitations under the License.
  *
  */
-var Webpack = require('webpack');
+var webpack = require('webpack');
 var path = require('path');
 var nodeModulesPath = path.resolve(__dirname, 'node_modules');
 var buildPath = path.resolve(__dirname, 'public', 'build');
@@ -23,6 +23,7 @@ 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 = {
@@ -64,10 +65,24 @@ var config = {
     },
     plugins: [
         new HtmlWebpackPlugin({
-            filename: '../index.html',
+            filename: '../index.html', 
             templateContent: '<div id="content"></div>'
-        }),
-        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+        })
     ]
 };
+
+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;
index c874218..df748a3 100644 (file)
@@ -15,6 +15,7 @@
     "babel-preset-es2015": "^6.3.13",
     "babel-preset-stage-0": "^6.3.13",
     "babel-runtime": "^6.3.19",
+    "compression-webpack-plugin": "^0.3.2",
     "express": "^4.13.3",
     "history": "^1.17.0",
     "json-loader": "^0.5.4",
index 39913a3..aec9106 100755 (executable)
@@ -27,6 +27,6 @@ echo 'Fetching third-party node_modules for '$PLUGIN_NAME
 npm install
 echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
 echo 'Packaging '$PLUGIN_NAME' using webpack'
-ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+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'
index e5728d0..7db6acc 100644 (file)
@@ -15,7 +15,7 @@
  *   limitations under the License.
  *
  */
-var Webpack = require('webpack');
+var webpack = require('webpack');
 var path = require('path');
 var nodeModulesPath = path.resolve(__dirname, 'node_modules');
 var buildPath = path.resolve(__dirname, 'public', 'build');
@@ -23,6 +23,7 @@ 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 = {
@@ -64,10 +65,24 @@ var config = {
     },
     plugins: [
         new HtmlWebpackPlugin({
-            filename: '../index.html'
-            , templateContent: '<div id="content"></div>'
-        }),
-        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+            filename: '../index.html', 
+            templateContent: '<div id="content"></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;
index d1ba614..e82fc7c 100644 (file)
@@ -799,8 +799,14 @@ NSR.addVnfrDataPromise = function(req, nsrs) {
                 try {
                     if (nsr["monitoring-param"]) {
                         nsr["monitoring-param"].map(function(m) {
-                            var vnfr = vnfrs[m["vnfr-id"]] || {};
+                            // var vnfr = vnfrs[m["vnfr-id"]] || {};
+                            // m["vnfr-name"] = vnfr['name'] ? vnfr['name'] : (vnfr['short-name'] ? vnfr['short-name'] : 'VNFR');
+                            var groupTag = m['group-tag'];
+                            var vnfrId = m['vnfr-mon-param-ref'] && m['vnfr-mon-param-ref'][0] && m['vnfr-mon-param-ref'][0]['vnfr-id-ref'];
+                            var vnfr = vnfrs[vnfrId] || {};
                             m["vnfr-name"] = vnfr['name'] ? vnfr['name'] : (vnfr['short-name'] ? vnfr['short-name'] : 'VNFR');
+                            m['group-tag'] = (groupTag ? (groupTag + ' - ') : '') + m['vnfr-name'] + (vnfrId ? ' (' + vnfrId.substring(1,8) + '...)' : '');
+
                         });
                     }
                 } catch (e) {
index 00b93f6..5353a8d 100644 (file)
@@ -40,6 +40,7 @@
     "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",
@@ -50,7 +51,7 @@
     "react-addons-css-transition-group": "^0.14.7",
     "sass-loader": "^3.1.2",
     "style-loader": "^0.13.0",
-    "webpack": "^1.3.0",
+    "webpack": "~1.14.0",
     "webpack-dev-server": "^1.10.1"
   }
 }
index 611fcc8..c59d28f 100755 (executable)
@@ -27,6 +27,6 @@ echo 'Fetching third-party node_modules for '$PLUGIN_NAME
 npm install
 echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
 echo 'Packaging '$PLUGIN_NAME' using webpack'
-ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+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'
index 517c70c..da79e8a 100644 (file)
@@ -54,5 +54,5 @@ CreateFleet.prototype.validateReset = function() {
   });
 };
 
-module.exports = alt.createStore(CreateFleet);
+module.exports = alt.createStore(CreateFleet, 'CreateFleet');
 
index b1d2163..aba84c5 100644 (file)
@@ -36,7 +36,7 @@ const PLATFORM = ROLES.PLATFORM;
 class InstantiateDashboard extends React.Component {
     constructor(props) {
         super(props);
-        this.Store = this.props.flux.stores.hasOwnProperty('InstantiateStore') ? this.props.flux.stores.InstantiateStore : this.props.flux.createStore(InstantiateStore                );
+        this.Store = this.props.flux.stores.hasOwnProperty('InstantiateStore') ? this.props.flux.stores.InstantiateStore : this.props.flux.createStore(InstantiateStore, 'InstantiateStore');
         this.state = this.Store.getState();
     }
     componentDidMount() {
index e779beb..3ec2a80 100644 (file)
@@ -20,7 +20,8 @@ import NetworkServiceSource from './launchNetworkServiceSource.js';
 import GUID from 'utils/guid.js';
 import AppHeaderActions from 'widgets/header/headerActions.js';
 import Alt from '../alt';
-import _ from 'lodash';
+import _cloneDeep from 'lodash/cloneDeep';
+import _find from 'lodash/find';
 
 
 class LaunchNetworkServiceStore {
@@ -640,7 +641,7 @@ class LaunchNetworkServiceStore {
             },
             updateSSHkeyRef: function(i, j, remove){
                 return function(e) {
-                    let usersList = _.cloneDeep(self.usersList)
+                    let usersList = _cloneDeep(self.usersList)
                     let keys = usersList[i]['ssh-authorized-key'];
                     if(!remove) {
                         let keyRef = JSON.parse(e.target.value).name;
@@ -672,7 +673,7 @@ class LaunchNetworkServiceStore {
 
         // Create a filtered NSD payload from the decorated one as RW.REST cannot handle extra parameters now
         let nsdPayload = {};
-        nsdPayload = _.cloneDeep(_.find(this.state.nsd[0].descriptors, {id: this.state.selectedNSDid}));
+        nsdPayload = _cloneDeep(_find(this.state.nsd[0].descriptors, {id: this.state.selectedNSDid}));
 
         if (nsdPayload != {}) {
             nsdPayload['meta'] && delete nsdPayload['meta'];
@@ -851,5 +852,4 @@ function getMockData() {
         pnfd: data.pnfd
     });
 }
-// export default Alt.createStore(LaunchNetworkServiceStore);
 export default LaunchNetworkServiceStore;
index c012309..88dd1bb 100644 (file)
@@ -25,7 +25,6 @@ import NsCardPanel from './nsCardPanel/nsCardPanel.jsx';
 import NsListPanel from './nsListPanel/nsListPanel.jsx';
 import Crouton from 'react-crouton'
 import AppHeader from 'widgets/header/header.jsx';
-import Utils from 'utils/utils.js';
 import './launchpad.scss';
 
 import {SkyquakeRBAC, isRBACValid} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
index b871f51..6de0e3b 100644 (file)
     }
 
     &-body {
-      -ms-flex-flow: col wrap;
-          flex-flow: col wrap;
+      -ms-flex-flow: column wrap;
+          flex-flow: column wrap;
     }
 
     .nsrSummary {
index cde7116..fff6fae 100644 (file)
@@ -106,7 +106,7 @@ module.exports = function(Alt) {
             return resolve(false);
           }
            $.ajax({
-            url: '/socket-polling',
+            url: '/socket-polling?api_server=' + API_SERVER,
             type: 'POST',
             beforeSend: Utils.addAuthorizationStub,
             data: {
index 520eebe..69255a4 100644 (file)
@@ -17,7 +17,9 @@
  *
  */
 import Alt from './alt';
-
+import _filter from 'lodash/filter';
+import _extend from 'lodash/extend';
+import _debounce from 'lodash/debounce';
 var FleetSource = require('./launchpadFleetSource.js');
 var FleetActions = require('./launchpadFleetActions.js');
 import CardActions from './launchpad_card/launchpadCardActions.js';
@@ -29,8 +31,6 @@ import {LaunchpadSettings} from './settings.js';
 
 
 var FleetStore;
-var _ = require('underscore');
-//  _.debounce(function(){});
 function FleetStoreConstructor() {
   var self = this;
   this.fleets = [];
@@ -134,7 +134,7 @@ FleetStoreConstructor.prototype.openNSRSocketSuccess = function(connection) {
       let deletingNSRs = [];
 
       if (self.nsrs) {
-        deletingNSRs = _.filter(self.nsrs, function(nsr) {
+        deletingNSRs = _filter(self.nsrs, function(nsr) {
           return nsr.deleting == true;
         });
       };
@@ -142,7 +142,7 @@ FleetStoreConstructor.prototype.openNSRSocketSuccess = function(connection) {
       deletingNSRs.forEach(function(deletingNSR) {
         data.nsrs.map(nsr => {
           if (nsr.id == deletingNSR.id) {
-            _.extend(nsr, deletingNSR);
+            _extend(nsr, deletingNSR);
           }
         });
       });
@@ -191,7 +191,7 @@ FleetStoreConstructor.prototype.deleteNsrInstanceError = function(data) {};
 FleetStoreConstructor.prototype.getNsrInstancesError = function(data) {
   console.log('ERROR', data)
 };
-FleetStoreConstructor.prototype.handleUpdateControlInput = _.debounce(function(data) {
+FleetStoreConstructor.prototype.handleUpdateControlInput = _debounce(function(data) {
   var opt = data[0];
   FleetStore.nsrControl(opt.operation, opt.url, data[1])
 }, 500).bind(null);
@@ -279,5 +279,5 @@ FleetStoreConstructor.prototype.getVDUConsoleLinkSuccess = function(data) {
   data['console-url'] && window.open(data['console-url']);
 }
 
-FleetStore = Alt.createStore(FleetStoreConstructor);
+FleetStore = Alt.createStore(FleetStoreConstructor, 'FleetStore');
 module.exports = FleetStore;
index 2a66540..5cdcb85 100644 (file)
@@ -18,6 +18,7 @@
 
 import React from 'react';
 import './jobListCard.scss'
+import TreeView from 'react-treeview';
 import Uptime from 'widgets/uptime/uptime.jsx';
 import Modal from 'react-awesome-modal';
 
@@ -57,16 +58,25 @@ class JobListCard extends React.Component {
     getJobDetails(job) {
         let jobDetails = null;
         if (job['job-status-details']) {
+            let jobDetailsArray = job['job-status-details'].split(/\\n/);
+            let jobDetailsText = [];
+            jobDetailsArray && jobDetailsArray.map((jobDetail) => {
+                jobDetailsText.push(jobDetail);
+                jobDetailsText.push(<br/>);
+            });
             jobDetails = (
                 <section className='jobListCard--details'>
                     <h4 onClick={this.openModal.bind(this)}>Job Details</h4>
                     <Modal
+                        className='jobListCard--details--modal'
                         visible={this.state.modalVisible}
                         width="600"
                         height="400"
                         effect="fadeInUp">
-                        <div>
-                            <div className='jobListCard--details--content'>{job['job-status-details']}</div>
+                        <div className='jobListCard--details--tree'>
+                            <TreeView nodeLabel={<span>Job Details</span>} key={'job-details'} defaultCollapsed={false}>
+                                <p>{jobDetailsText}</p>
+                            </TreeView>
                             <h4 className='jobListCard--details--close' onClick={this.closeModal.bind(this)}>Close</h4>
                         </div>
                     </Modal>
index 8551abb..0ea403f 100644 (file)
     }
     &--details {
         font-size:0.75rem;
+
+        >div {
+            display: flex;
+            >div {
+                display: flex;
+                padding: 1em 0 0 1em;
+            }
+        }
+
+        &--tree {
+                display: flex;
+                flex-direction: column;
+
+                .tree-view {
+                    min-height: 300px;
+                    min-width: 580px;
+                    overflow-y: scroll;
+                }
+            }
+
         h4 {
             padding-bottom:0.5rem;
             text-decoration:underline;
index b44b3d9..9d0cdba 100644 (file)
@@ -43,7 +43,7 @@ class NsrConfigPrimitives extends React.Component {
                 vnfrConfigPrimitive.name = vnfr['short-name'];
                 vnfrConfigPrimitive['vnfr-id-ref'] = vnfr['id'];
                 //input references
-                let configPrimitives = vnfr['vnf-configuration']['service-primitive'];
+                let configPrimitives = vnfr['vnf-configuration'] && vnfr['vnf-configuration']['service-primitive'];
                 //input references by key
                 let vnfrConfigPrimitives = {}
 
index ac54e7f..db94ded 100644 (file)
  */
 import React from 'react';
 import RecordViewStore from '../recordViewer/recordViewStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
 import Button from 'widgets/button/rw.button.js';
 import Utils from 'utils/utils.js';
-import _ from 'underscore';
 import UpTime from 'widgets/uptime/uptime.jsx';
 import './nsrScalingGroups.scss';
 
-export default class NsrScalingGroups extends React.Component {
+class NsrScalingGroups extends React.Component {
        constructor(props) {
                super(props);
                this.state = {};
        }
 
-       handleExecuteClick = (nsr_id, scaling_group_id, event) => {
+       handleExecuteClick = (nsr_id, scaling_group_id, max_instance_count, event) => {
+               let self = this;
+               if (this.getInstancesForScalingGroup(scaling_group_id) == max_instance_count) {
+                       self.props.flux.actions.global.showNotification("Maximum allowed scaling instances created for this group. Cannot create any more");
+                       return;
+               }
                RecordViewStore.createScalingGroupInstance({
                        nsr_id: nsr_id,
                        scaling_group_id: scaling_group_id
@@ -133,7 +138,7 @@ export default class NsrScalingGroups extends React.Component {
 
                        let sgInstanceTable = this.createScalingGroupTable(sgd.name);
 
-                       let sgCreateInstanceButton = <Button label='Create Scaling Group Instance' className="dark" isDisabled={this.getInstancesForScalingGroup(sgd.name) == sgd["max-instance-count"]} isLoading={false} onClick={this.handleExecuteClick.bind(this, this.props.data.id, sgd.name)} />
+                       let sgCreateInstanceButton = <Button label='Create Scaling Group Instance' className="dark" isDisabled={this.getInstancesForScalingGroup(sgd.name) == sgd["max-instance-count"]} isLoading={false} onClick={this.handleExecuteClick.bind(this, this.props.data.id, sgd.name, sgd['max-instance-count'])} />
 
                        let scalingGroup =
                                <div>
@@ -158,3 +163,5 @@ export default class NsrScalingGroups extends React.Component {
        }
 
 }
+
+export default SkyquakeComponent(NsrScalingGroups);
index 7b3cb21..7fad6f4 100644 (file)
@@ -32,7 +32,7 @@ import PlacementGroupsInfo from './placementGroupsInfo.jsx';
 import JobListCard from '../launchpad_card/jobListCard.jsx';
 import NSVirtualLinks from '../virtual_links/nsVirtualLinks.jsx';
 import LaunchpadFleetStore from '../launchpadFleetStore.js';
-
+import _forEach from 'lodash/forEach';
 import Prism from 'prismjs';
 import 'prismjs/themes/prism.css';
 
@@ -148,12 +148,12 @@ export default class RecordCard extends React.Component {
                 //     </pre>
                 function buildProperties(obj) {
                   let p = [];
-                    _.forEach(obj, function(v, k) {
+                    _forEach(obj, function(v, k) {
                     p.push(
-                      <div style={{margin: '0.5rem 0.5rem'}} key={k + vi}>
+                      <div style={{margin: '0.5rem 0.5rem'}} key={k + '-' + vi}>
                         <div style={{margin: '0 0.5rem',
     fontWeight: 'bold', textTransform: 'uppercase', color: '#5b5b5b'}}>{k}</div>
-                        <div style={{margin: '0 0.5rem'}}>{v.constructor.name != 'Object' ? v : buildProperties(v)}</div>
+                        <div style={{margin: '0 0.5rem'}}>{(v.constructor.name == 'String' || v.constructor.name == 'Number') ? v : buildProperties(v)}</div>
                       </div>
                     )
                   });
@@ -272,7 +272,7 @@ export default class RecordCard extends React.Component {
       if(this.props.isLoading) {
         html = <DashboardCard className="loading" showHeader={true} title={cardData["short-name"]}><LoadingIndicator size={10} show={true} /></DashboardCard>
       } else {
-        let glyphValue = (this.props.mmmrecordDetailsToggleValue) ? "chevron-left" : "chevron-right";
+        let glyphValue = (!this.props.recordDetailsToggleValue) ? "chevron-left" : "chevron-right";
 
         if (this.props.type == 'nsr') {
           tabList.push(
index a3b2d67..49c3122 100644 (file)
@@ -94,7 +94,7 @@ export default {
                 return new Promise(function(resolve, reject) {
                     console.log('Getting NSR Socket');
                     $.ajax({
-                        url: '/socket-polling',
+                        url: '/socket-polling?api_server=' + API_SERVER,
                         type: 'POST',
                         beforeSend: Utils.addAuthorizationStub,
                         data: {
@@ -117,7 +117,7 @@ export default {
                 return new Promise(function(resolve, reject) {
                     console.log('Getting Job Socket');
                     $.ajax({
-                        url: '/socket-polling',
+                        url: '/socket-polling?api_server=' + API_SERVER,
                         type: 'POST',
                         beforeSend: Utils.addAuthorizationStub,
                         data: {
@@ -139,7 +139,7 @@ export default {
                 return new Promise(function(resolve, reject) {
                     console.log('Getting VNFR Socket for: ' + state.recordID);
                     $.ajax({
-                        url: '/socket-polling',
+                        url: '/socket-polling?api_server=' + API_SERVER,
                         type: 'POST',
                         beforeSend: Utils.addAuthorizationStub,
                         data: {
index a7770a7..72609e4 100644 (file)
@@ -20,7 +20,9 @@ import RecordViewSource from './recordViewSource.js';
 // import source
 // import AppHeaderActions from 'widgets/header/headerActions.js';
 import Alt from '../alt';
-import _ from 'underscore';
+import _find from 'lodash/find';
+import _indexOf from 'lodash/indexOf';
+import _extend from 'lodash/extend';
 
 class RecordViewStore {
     constructor() {
@@ -429,7 +431,7 @@ function connectionManager(type, connection) {
 
                     sgInstance['vnfrs'] && sgInstance['vnfrs'].map((vnfr, vnfrIndex) => {
                         scaledVnfrs.push(vnfr);
-                        let vnfrObj = _.findWhere(nsrs.vnfrs, {id: vnfr});
+                        let vnfrObj = _find(nsrs.vnfrs, {id: vnfr});
                         scaledVnfNav.vnfr.push({
                             name: vnfrObj['short-name'],
                             id: vnfr,
@@ -444,7 +446,7 @@ function connectionManager(type, connection) {
 
             // Non-scaled VNFRs
             nsrs.vnfrs.map(function(vnfr, vnfrIndex) {
-                if (_.indexOf(scaledVnfrs, vnfr.id) == -1) {
+                if (_indexOf(scaledVnfrs, vnfr.id) == -1) {
                     nav.push({
                         name: vnfr["short-name"],
                         id: vnfr.id,
@@ -461,7 +463,7 @@ function connectionManager(type, connection) {
             };
         }
 
-        navigatorState = _.extend(navigatorState, {
+        navigatorState = _extend(navigatorState, {
             recordData: recordData,
             recordType: type,
             cardLoading: false,
@@ -472,4 +474,4 @@ function connectionManager(type, connection) {
     };
 }
 
-export default Alt.createStore(RecordViewStore);
+export default Alt.createStore(RecordViewStore, 'RecordViewStore');
index 7ae421b..c62a1b4 100644 (file)
@@ -20,7 +20,8 @@ import SshKeySource from './sshKeySource.js';
 import GUID from 'utils/guid.js';
 import AppHeaderActions from 'widgets/header/headerActions.js';
 import Alt from '../alt';
-import _ from 'lodash';
+import _cloneDeep from 'lodash/cloneDeep';
+import _merge from 'lodash/merge';
 
 
 export default class SshKeyStore {
@@ -29,7 +30,7 @@ export default class SshKeyStore {
             keys: [],
             entities: {}
         };
-        this.dataCache = _.cloneDeep(this.data);
+        this.dataCache = _cloneDeep(this.data);
         this.newKey = {
           name: '',
           key: ''
@@ -67,7 +68,7 @@ export default class SshKeyStore {
     cancelEditSshKeyPair = (k) => {
         let self = this;
         return function(e) {
-            let data = _.cloneDeep(self.data);
+            let data = _cloneDeep(self.data);
             data.entities[k].key = self.dataCache.entities[k].key;
             data.entities[k].isEditable = false;
             self.setState({data:data});
@@ -106,7 +107,7 @@ export default class SshKeyStore {
             isEditable: false
         };
         this.setState({
-            dataCache: _.cloneDeep(keys),
+            dataCache: _cloneDeep(keys),
             data: keys,
             newKey: {
               name: '',
@@ -122,7 +123,7 @@ export default class SshKeyStore {
             isEditable: false
         };
         this.setState({
-            dataCache: _.cloneDeep(keys),
+            dataCache: _cloneDeep(keys),
             data: keys,
             newKey: {
               name: '',
@@ -135,7 +136,7 @@ export default class SshKeyStore {
         keys.keys.splice(keys.keys.indexOf(data.name), 1);
         delete keys.entities[data.name];
         this.setState({
-            dataCache: _.cloneDeep(keys),
+            dataCache: _cloneDeep(keys),
             data: keys
         })
     }
@@ -146,7 +147,7 @@ export default class SshKeyStore {
         let flattened = this.flattenKeys(data);
         this.setState({
             data: flattened,
-            dataCache: _.cloneDeep(flattened)
+            dataCache: _cloneDeep(flattened)
         })
     }
     updateSshKeyPair = (k, field) => {
@@ -166,7 +167,7 @@ export default class SshKeyStore {
         };
         data && data.map(function(d){
             fd.keys.push(d.name);
-            fd.entities[d.name] = _.merge({isEditable: false}, d)
+            fd.entities[d.name] = _merge({isEditable: false}, d)
         });
         return fd;
     }
index f832215..3d4179e 100644 (file)
@@ -27,7 +27,7 @@ import '../../node_modules/open-iconic/font/css/open-iconic.css';
 class SshKeys extends Component {
     constructor(props) {
         super(props);
-        this.Store = this.props.flux.stores.hasOwnProperty('SshKeyStore') ? this.props.flux.stores.SshKeyStore : this.props.flux.createStore(SshKeyStore);
+        this.Store = this.props.flux.stores.hasOwnProperty('SshKeyStore') ? this.props.flux.stores.SshKeyStore : this.props.flux.createStore(SshKeyStore, 'SshKeyStore');
         this.state = this.Store.getState();
         this.Store.listen(this.handleUpdate);
     }
index 9436671..459f4af 100644 (file)
@@ -20,7 +20,7 @@ import LoadingIndicator from 'widgets/loading-indicator/loadingIndicator.jsx';
 import DashboardCard from 'widgets/dashboard_card/dashboard_card.jsx';
 import Listy from 'widgets/listy/listy.js';
 
-import _ from 'underscore';
+import _isEmpty from 'lodash/isEmpty';
 
 export default class TopologyDetail extends React.Component {
     constructor(props) {
@@ -28,7 +28,7 @@ export default class TopologyDetail extends React.Component {
     }
 
     detailData(data) {
-        if (_.isEmpty(data)) {
+        if (_isEmpty(data)) {
             return {};
         } else {
             return {
index fd38d84..756c775 100644 (file)
@@ -41,7 +41,7 @@ export default {
             return resolve(false);
           }
            $.ajax({
-            url: '/socket-polling',
+            url: '/socket-polling?api_server=' + API_SERVER,
             type: 'POST',
             beforeSend: Utils.addAuthorizationStub,
             data: {
index 2d14952..b6bed73 100644 (file)
@@ -132,4 +132,4 @@ class TopologyL2Store {
         });
     }
 }
-    export default Alt.createStore(TopologyL2Store);
+    export default Alt.createStore(TopologyL2Store, 'TopologyL2Store');
index 04e1c66..b570082 100644 (file)
@@ -42,7 +42,7 @@ export default {
             return resolve(false);
           }
            $.ajax({
-            url: '/socket-polling',
+            url: '/socket-polling?api_server=' + API_SERVER,
             type: 'POST',
             beforeSend: Utils.addAuthorizationStub,
             data: {
index 10c5005..5d6cef9 100644 (file)
@@ -135,4 +135,4 @@ class TopologyStore {
     }
 
 }
-export default Alt.createStore(TopologyStore);
+export default Alt.createStore(TopologyStore, 'TopologyStore');
index d3dc011..bec414e 100644 (file)
@@ -17,7 +17,6 @@
  */
 import React from 'react';
 import Utils from 'utils/utils.js';
-import _ from 'lodash';
 import './nsVirtualLinks.scss';
 import NSVirtualLinkCreateStore from './nsVirtualLinkCreateStore.js';
 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
@@ -31,7 +30,8 @@ import SelectOption from 'widgets/form_controls/selectOption.jsx';
 class NsVirtualLinkCreate extends React.Component {
        constructor(props) {
                super(props);
-           this.Store = this.props.flux.stores.hasOwnProperty('NSVirtualLinkCreateStore') ? this.props.flux.stores.NSVirtualLinkCreateStore : this.props.flux.createStore(NSVirtualLinkCreateStore);
+           this.Store = this.props.flux.stores.hasOwnProperty('NSVirtualLinkCreateStore') ? 
+                               this.props.flux.stores.NSVirtualLinkCreateStore : this.props.flux.createStore(NSVirtualLinkCreateStore, 'NSVirtualLinkCreateStore');
                this.state = this.Store.getState();
                this.Store.listen(this.handleUpdate);
        }
index dbd7f58..ac050e4 100644 (file)
@@ -18,7 +18,9 @@
 import NSVirtualLinkCreateActions from './nsVirtualLinkCreateActions.js';
 import NSVirtualLinkCreateSource from './nsVirtualLinkCreateSource.js';
 import Alt from '../alt';
-import _ from 'lodash';
+import _cloneDeep from 'lodash/cloneDeep';
+import _pickBy from 'lodash/pickBy';
+import _identity from 'lodash/identity';
 
 class NSVirtualLinkCreateStore {
        constructor() {
@@ -141,7 +143,7 @@ class NSVirtualLinkCreateStore {
        }
 
        updateFirstLevelKey = (key, e) => {
-               let vld = _.cloneDeep(this.vld);
+               let vld = _cloneDeep(this.vld);
                let value = e.target.nodeName == "SELECT" ? JSON.parse(e.target.value) : e.target.value;
                vld[key] = value;
                this.setState({
@@ -150,7 +152,7 @@ class NSVirtualLinkCreateStore {
        }
 
        updateSecondLevelKey = (firstLevelKey, secondLevelKey, e) => {
-               let vld = _.cloneDeep(this.vld);
+               let vld = _cloneDeep(this.vld);
                if (!vld[firstLevelKey]) {
                        vld[firstLevelKey] = {};
                }
@@ -176,7 +178,7 @@ class NSVirtualLinkCreateStore {
        }
 
        updateVLDInitParamsValue = (currentVLDInitParamsType, e) => {
-               let vld = _.cloneDeep(this.vld);
+               let vld = _cloneDeep(this.vld);
                this.vldInitParamsTypes.map((vldInitParamType) => {
                        if (currentVLDInitParamsType == vldInitParamType) {
                                let value = e.target.nodeName == "SELECT" ? JSON.parse(e.target.value) : e.target.value;
@@ -192,7 +194,7 @@ class NSVirtualLinkCreateStore {
        }
 
        updateFirstLevelListKeyChange = (listName, index, keyName, e) => {
-               let vld = _.cloneDeep(this.vld);
+               let vld = _cloneDeep(this.vld);
                
 
                !vld[listName] && (vld[listName] = []);
@@ -207,7 +209,7 @@ class NSVirtualLinkCreateStore {
        addConnectionPointRef = () => {
                let vld = {};
                if (this.vld) {
-                       vld = _.cloneDeep(this.vld);
+                       vld = _cloneDeep(this.vld);
                        if (!vld['vnfd-connection-point-ref']) {
                                vld['vnfd-connection-point-ref'] = [];
                        }
@@ -224,7 +226,7 @@ class NSVirtualLinkCreateStore {
        }
 
        removeConnectionPointRef = (vnfdConnectionPointRefIndex) => {
-               let vld = _.cloneDeep(this.vld);
+               let vld = _cloneDeep(this.vld);
                vld['vnfd-connection-point-ref'].splice(vnfdConnectionPointRefIndex, 1);
                this.setState({
                        vld: vld
@@ -258,7 +260,7 @@ class NSVirtualLinkCreateStore {
 
        cleanupPayload = (mode, vld) => {
                // Do necessary cleanup here
-               let cleanVld = _.pickBy(vld, _.identity);
+               let cleanVld = _pickBy(vld, _identity);
                return cleanVld;
        }
 
index 33404e0..c6a45e7 100644 (file)
@@ -18,7 +18,7 @@
 import React from 'react';
 import RecordViewStore from '../recordViewer/recordViewStore.js';
 import Utils from 'utils/utils.js';
-import _ from 'lodash';
+import _isArray from 'lodash/isArray';
 import './nsVirtualLinks.scss';
 import UpTime from 'widgets/uptime/uptime.jsx';
 import NSVirtualLinksStore from './nsVirtualLinksStore.js';
@@ -64,7 +64,7 @@ class NsVirtualLinkDetails extends React.Component {
                                let value = this.resolvePath(this.props.virtualLink, field.key);
                                let textFields = [];
 
-                               if (_.isArray(value)) {
+                               if (_isArray(value)) {
                                        value.map((v, idx) => {
                                                let transformedValue = this.transformValue(field, v);
                                                textFields.push(
index fa09d9f..37cf0b3 100644 (file)
@@ -19,7 +19,7 @@ import React from 'react';
 import NSVirtualLinkCreateStore from './nsVirtualLinkCreateStore.js';
 import Button from 'widgets/button/rw.button.js';
 import Utils from 'utils/utils.js';
-import _ from 'lodash';
+import _find from 'lodash/find';
 import './nsVirtualLinks.scss';
 import UpTime from 'widgets/uptime/uptime.jsx';
 import NSVirtualLinkDetails from './nsVirtualLinkDetails.jsx';
@@ -29,7 +29,8 @@ import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx'
 class NsVirtualLinks extends React.Component {
        constructor(props) {
                super(props);
-           this.Store = this.props.flux.stores.hasOwnProperty('NSVirtualLinkCreateStore') ? this.props.flux.stores.NSVirtualLinkCreateStore : this.props.flux.createStore(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'
                this.selectedVirtualLink = null;
@@ -113,7 +114,7 @@ class NsVirtualLinks extends React.Component {
        handleSelectVirtualLinkClick = (virtualLinkId, event) => {
                this.setState({
                        mode: 'viewing',
-                       selectedVirtualLink: this.props.data && this.props.data['decorated-vlrs'] && _.find(this.props.data['decorated-vlrs'], {id: virtualLinkId}),
+                       selectedVirtualLink: this.props.data && this.props.data['decorated-vlrs'] && _find(this.props.data['decorated-vlrs'], {id: virtualLinkId}),
                        editingVirtualLink: null
                });
        }
@@ -121,8 +122,8 @@ class NsVirtualLinks extends React.Component {
                event.stopPropagation();
                this.setState({
                        mode: 'editing',
-                       editingVirtualLink: this.props.data && this.props.data['nsd'] && this.props.data['nsd']['vld'] && _.find(this.props.data['nsd']['vld'], {id: vldId}),
-                       selectedVirtualLink: this.props.data && this.props.data['decorated-vlrs'] && _.find(this.props.data['decorated-vlrs'], {id: vlrId})
+                       editingVirtualLink: this.props.data && this.props.data['nsd'] && this.props.data['nsd']['vld'] && _find(this.props.data['nsd']['vld'], {id: vldId}),
+                       selectedVirtualLink: this.props.data && this.props.data['decorated-vlrs'] && _find(this.props.data['decorated-vlrs'], {id: vlrId})
                });
        }
 
index 782f322..327bdb9 100644 (file)
@@ -42,7 +42,7 @@ export default {
           }
           console.log(nsr_id)
           $.ajax({
-            url: '//' + window.location.hostname + ':' + window.location.port + '/socket-polling',
+            url: '//' + window.location.hostname + ':' + window.location.port + '/socket-polling?api_server=' + API_SERVER,
             type: 'POST',
             beforeSend: Utils.addAuthorizationStub,
             data: {
index 9a64531..5e09d7a 100644 (file)
@@ -63,4 +63,4 @@ class VnfrStore {
   }
 };
 
-export default alt.createStore(VnfrStore)
+export default alt.createStore(VnfrStore, 'VnfrStore')
index 3984a58..ed2a23f 100644 (file)
@@ -15,7 +15,7 @@
  *   limitations under the License.
  *
  */
-var Webpack = require('webpack');
+var webpack = require('webpack');
 var path = require('path');
 var nodeModulesPath = path.resolve(__dirname, 'node_modules');
 var buildPath = path.resolve(__dirname, 'public', 'build');
@@ -23,7 +23,8 @@ var mainPath = path.resolve(__dirname, 'src', 'main.js');
 var uiPluginCmakeBuild = process.env.ui_plugin_cmake_build || false;
 var frameworkPath = uiPluginCmakeBuild?'../../../../skyquake/skyquake-build/framework':'../../framework';
 var HtmlWebpackPlugin = require('html-webpack-plugin');
-var CommonsPlugin = new require("webpack/lib/optimize/CommonsChunkPlugin")
+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 = {
@@ -66,10 +67,24 @@ var config = {
     },
     plugins: [
         new HtmlWebpackPlugin({
-            filename: '../index.html'
-            , templateContent: '<div id="app"></div>'
-        }),
-        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+            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;
index ae4ef97..d58e262 100644 (file)
@@ -37,6 +37,7 @@
     "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",
index 4b23c01..569bc06 100755 (executable)
@@ -27,6 +27,6 @@ echo 'Fetching third-party node_modules for '$PLUGIN_NAME
 npm install
 echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
 echo 'Packaging '$PLUGIN_NAME' using webpack'
-ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+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'
index 7b50c76..0aced4e 100644 (file)
@@ -16,7 +16,9 @@
  *
  */
 import React from 'react';
-import _ from 'lodash';
+import _isEmpty from 'lodash/isEmpty';
+import _find from 'lodash/find';
+import _cloneDeep from 'lodash/cloneDeep';
 import './logging.scss';
 
 import Button from 'widgets/button/rw.button.js';
@@ -199,7 +201,7 @@ export default class LoggingGeneral extends React.Component {
   getData() {
     LoggingStore.getLoggingConfig();
     this.setState({
-      isLoading: _.isEmpty(this.state.loggingConfig)
+      isLoading: _isEmpty(this.state.loggingConfig)
     });
   }
   componentWillUnmount = () => {
@@ -239,7 +241,7 @@ export default class LoggingGeneral extends React.Component {
     this.context.router.push({pathname: ''});
   }
   // removeCategoryNulls(config) {
-  //   let cleanConfig = _.cloneDeep(config);
+  //   let cleanConfig = _cloneDeep(config);
   //   let cleanSeverities = [];
   //   config.defaultSeverities.map(function(d) {
   //     if (d.severity) {
@@ -250,7 +252,7 @@ export default class LoggingGeneral extends React.Component {
   //   return cleanConfig;
   // }
   cleanupConfig(config) {
-    let cleanConfig = _.cloneDeep(config);
+    let cleanConfig = _cloneDeep(config);
     let cleanSeverities = [];
     cleanConfig.defaultSeverities && cleanConfig.defaultSeverities.map((defSev) => {
       if (defSev.severity) {
@@ -360,14 +362,14 @@ export default class LoggingGeneral extends React.Component {
     // for RIFT-14856 so that default severities map to syslog sink
     
     // Find first syslog sink with (WTF - no type on sinks!) name syslog.
-    let syslogSink = this.state.loggingConfig.sinks && _.find(this.state.loggingConfig.sinks, {
+    let syslogSink = this.state.loggingConfig.sinks && _find(this.state.loggingConfig.sinks, {
       name: 'syslog'
     });
     let defaultSyslogSeverities = [];
 
     this.state.loggingConfig && this.state.loggingConfig.defaultSeverities && this.state.loggingConfig.defaultSeverities.map((defaultSeverity) => {
       // Mapping between default categories and names inside a sink
-      let syslogFilterCategory = (syslogSink.filter && syslogSink.filter.category && _.find(syslogSink.filter.category, {
+      let syslogFilterCategory = (syslogSink.filter && syslogSink.filter.category && _find(syslogSink.filter.category, {
         name: defaultSeverity.category
       })) || {
         name: defaultSeverity.category,
index 6743b92..3c306ae 100644 (file)
@@ -15,7 +15,9 @@
  *   limitations under the License.
  *
  */
-import _ from 'lodash';
+import _cloneDeep from 'lodash/cloneDeep';
+import _findIndex from 'lodash/findIndex';
+import _remove from 'lodash/remove';
 import LoggingActions from './loggingActions.js';
 import LoggingSource from './loggingSource.js';
 
@@ -44,7 +46,7 @@ class LoggingStore {
   getLoggingConfigSuccess = (data) => {
       console.log("LoggingStore.getLoggingConfigSuccess called. data=", data);
       // Do we need to do a deep clone?
-      const initialLoggingConfig = _.cloneDeep(data);
+      const initialLoggingConfig = _cloneDeep(data);
       console.log("initialLoggingConfig=", initialLoggingConfig);
       this.setState({
       loggingConfig: data,
@@ -59,7 +61,7 @@ class LoggingStore {
 
   putLoggingConfigSuccess = (data) => {
     console.log("LoggingStore.putLoggingConfigSuccess called. data=", data);
-    const initialLoggingConfig = _.cloneDeep(this.loggingConfig);
+    const initialLoggingConfig = _cloneDeep(this.loggingConfig);
     this.setState({
       isLoading: false,
       initialLoggingConfig: initialLoggingConfig
@@ -73,7 +75,7 @@ class LoggingStore {
   resetLoggingConfigData = (data) => {
     console.log('LoggingStore.resetLoggingConfigData called. data=', data);
     // Do we need to do a deep clone?
-    const loggingConfig = _.cloneDeep(this.initialLoggingConfig);
+    const loggingConfig = _cloneDeep(this.initialLoggingConfig);
     this.setState({
       loggingConfig: loggingConfig
     });
@@ -83,7 +85,7 @@ class LoggingStore {
     console.log("LoggingStore.updateCategoryDefaultSeverity:", catsev);
     // find the category
 
-    let catIndex = _.findIndex(this.loggingConfig.defaultSeverities, function(o) {
+    let catIndex = _findIndex(this.loggingConfig.defaultSeverities, function(o) {
       return o.category == catsev.category;
     });
     console.log("catIndex=", catIndex);
@@ -104,7 +106,7 @@ class LoggingStore {
     // find the category (name) in the syslog sink
 
     let self = this;
-    let loggingConfig = _.cloneDeep(this.loggingConfig);
+    let loggingConfig = _cloneDeep(this.loggingConfig);
     let syslogSinkIndex = -1;
     let nulledCategories = this.nulledCategories;
 
@@ -112,7 +114,7 @@ class LoggingStore {
       if (sink.name == 'syslog') {
         if (sink.filter) {
           if (sink.filter.category) {
-            let catIndex = _.findIndex(sink.filter.category, {
+            let catIndex = _findIndex(sink.filter.category, {
               name: catsev.name
             });
             if (catIndex != -1) {
@@ -127,14 +129,14 @@ class LoggingStore {
                 // missing elements is not allowed!
                 // When backend supports it, in loggingSource change the order of operations
                 // // Delete first followed by save/put.
-                _.remove(loggingConfig.sinks[sinkIndex].filter.category, {
+                _remove(loggingConfig.sinks[sinkIndex].filter.category, {
                   name: catsev.name
                 });
               } else {
                 sink.filter.category[catIndex] = catsev;
               }
             } else {
-              _.remove(nulledCategories, (v) => v == catsev.name);
+              _remove(nulledCategories, (v) => v == catsev.name);
               this.setState({
                 nulledCategories: nulledCategories
               });
@@ -216,4 +218,4 @@ class LoggingStore {
   }
 }
 
-export default alt.createStore(LoggingStore);
+export default alt.createStore(LoggingStore, 'LoggingStore');
index 87e91c7..a4e4834 100644 (file)
@@ -15,7 +15,7 @@
  *   limitations under the License.
  *
  */
-var Webpack = require('webpack');
+var webpack = require('webpack');
 var path = require('path');
 var nodeModulesPath = path.resolve(__dirname, 'node_modules');
 var buildPath = path.resolve(__dirname, 'public', 'build');
@@ -23,6 +23,7 @@ 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 = {
@@ -65,10 +66,24 @@ var config = {
     },
     plugins: [
         new HtmlWebpackPlugin({
-            filename: '../index.html'
-            , templateContent: '<div id="app"></div>'
-        }),
-        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+            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;
index 044268d..9d9d635 100755 (executable)
@@ -38,7 +38,7 @@ for f in *; do
         npm install
         echo 'Fetching third-party node_modules for '$f'...done'
         echo 'Packaging '$f' using webpack'
-        ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+        ./node_modules/.bin/webpack --optimize-minimize --progress --config webpack.production.config.js
         echo 'Packaging '$f' using webpack... done'
         cd ..
         echo 'Building plugin '$f'... done'
index 2396e1a..6019357 100644 (file)
@@ -272,6 +272,13 @@ if (cluster.isMaster && clusteredLaunch) {
                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();
+           });
+
        }
 
        /**
index b7e0e57..143dfe8 100644 (file)
@@ -11,7 +11,7 @@ import Alt from '../../framework/widgets/skyquake_container/skyquakeAltInstance.
 import {Panel, PanelWrapper} from '../../framework/widgets/panel/panel.jsx'
 import '../../node_modules/open-iconic/font/css/open-iconic.css';
 import 'style/base.scss';
-const Store = Alt.createStore(InstantiateStore)
+const Store = Alt.createStore(InstantiateStore, 'InstantiateStore');
 // import StyleGuideItem from 'react-style-guide';
 // import '../../node_modules/react-style-guide/node_modules/highlight.js/styles/github.css';
 let SampleNSD = {
index 649ec4a..dcaf417 100644 (file)
@@ -11,7 +11,7 @@ import 'style/base.scss';
 // import StyleGuideItem from 'react-style-guide';
 // import '../../node_modules/react-style-guide/node_modules/highlight.js/styles/github.css';
 
-const Store = Alt.createStore(SshKeyStore)
+const Store = Alt.createStore(SshKeyStore, 'SshKeyStore');
 
 storiesOf('CatalogCard', module)
 // .add('page', () => (<SshKeys />))