NOTICKET: Merging OSM/master to OSM/projects

Signed-off-by: KIRAN KASHALKAR <kiran.kashalkar@riftio.com>
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..40dc922
--- /dev/null
+++ b/Dockerfile
@@ -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
index 0000000..dda86ae
--- /dev/null
+++ b/Jenkinsfile
@@ -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"
+			}
+		}
+	}
+}
diff --git a/skyquake/framework/utils/rw.js b/skyquake/framework/utils/rw.js
index 1cca8f2..0c280c1 100644
--- a/skyquake/framework/utils/rw.js
+++ b/skyquake/framework/utils/rw.js
@@ -30,6 +30,11 @@
  *     ['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 @@
   },
 
   sum : function(total, i, key, value) {
-    if (_.isNumber(value)) {
+    if (_isNumber(value)) {
       total[key] = (i === 0 ? value : (total[key] + value));
     }
   },
@@ -312,7 +317,7 @@
   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 @@
   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 @@
       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.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 @@
     }
     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.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 @@
   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 @@
   },
 
   jpath : function(jpath, n) {
-    return _.flatten(jsonPath.eval(n || this, jpath), true);
+    return _flatten(jsonPath.eval(n || this, jpath), true);
   }
 };
 
diff --git a/skyquake/framework/utils/utils.js b/skyquake/framework/utils/utils.js
index 16037d7..ed1f113 100644
--- a/skyquake/framework/utils/utils.js
+++ b/skyquake/framework/utils/utils.js
@@ -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 = {};
diff --git a/skyquake/framework/widgets/button/rw.button.js b/skyquake/framework/widgets/button/rw.button.js
index 41730eb..5d684ec 100644
--- a/skyquake/framework/widgets/button/rw.button.js
+++ b/skyquake/framework/widgets/button/rw.button.js
@@ -251,7 +251,8 @@
         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)
diff --git a/skyquake/framework/widgets/header/header.jsx b/skyquake/framework/widgets/header/header.jsx
index 21f4c10..3cfaaf6 100644
--- a/skyquake/framework/widgets/header/header.jsx
+++ b/skyquake/framework/widgets/header/header.jsx
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -55,6 +55,7 @@
                 type={"error"}
                 hidden={!(this.state.validateErrorEvent && this.state.validateErrorMsg)}
                 onDismiss={HeaderStore.validateReset}
+                timeout= {5000}
             />
 
         // html = (
diff --git a/skyquake/framework/widgets/header/headerStore.js b/skyquake/framework/widgets/header/headerStore.js
index 4ee86ff..150b325 100644
--- a/skyquake/framework/widgets/header/headerStore.js
+++ b/skyquake/framework/widgets/header/headerStore.js
@@ -43,4 +43,4 @@
     }
 }
 
-export default Alt.createStore(HeaderStoreConstructor)
+export default Alt.createStore(HeaderStoreConstructor, 'HeaderStoreConstructor')
diff --git a/skyquake/framework/widgets/listy/listy.js b/skyquake/framework/widgets/listy/listy.js
index ac0e58a..d032f97 100644
--- a/skyquake/framework/widgets/listy/listy.js
+++ b/skyquake/framework/widgets/listy/listy.js
@@ -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 @@
 
  		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 @@
 				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 @@
 
  		return React.createElement("div", {
  			className: "listy" },
- 			_.isEmpty(data) ? 
+ 			_isEmpty(data) ? 
  			this.noDataMessage() : 
  			this.createList(data)
  		)
@@ -116,7 +118,7 @@
 
 	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);
diff --git a/skyquake/framework/widgets/login/login.jsx b/skyquake/framework/widgets/login/login.jsx
index 1506809..840a2bd 100644
--- a/skyquake/framework/widgets/login/login.jsx
+++ b/skyquake/framework/widgets/login/login.jsx
@@ -19,7 +19,8 @@
 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);
diff --git a/skyquake/framework/widgets/skyquake_container/eventCenter.jsx b/skyquake/framework/widgets/skyquake_container/eventCenter.jsx
index 75d2d52..7df4e3e 100644
--- a/skyquake/framework/widgets/skyquake_container/eventCenter.jsx
+++ b/skyquake/framework/widgets/skyquake_container/eventCenter.jsx
@@ -28,7 +28,9 @@
 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 @@
 		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 @@
 		}
 
 		if (notificationList) {
-			stateObject.notifications = _.merge(notificationList, props.notifications);
+			stateObject.notifications = _merge(notificationList, props.notifications);
 		} else {
 			stateObject.notifications = props.notifications;
 		}
@@ -112,7 +114,7 @@
 			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];
 				}
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx b/skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx
index 6347c8f..8b61f4b 100644
--- a/skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx
@@ -24,7 +24,6 @@
 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 @@
                             type={notificationType}
                             hidden={!(displayNotification && notificationMessage)}
                             onDismiss={SkyquakeContainerActions.hideNotification}
+                            timeout= {5000}
                         />
                         <ScreenLoader show={displayScreenLoader}/>
                         <SkyquakeNav nav={nav}
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeContainerSource.js b/skyquake/framework/widgets/skyquake_container/skyquakeContainerSource.js
index a26de2b..8afaf83 100644
--- a/skyquake/framework/widgets/skyquake_container/skyquakeContainerSource.js
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeContainerSource.js
@@ -18,13 +18,14 @@
 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 @@
             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: {
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js b/skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js
index 4f27094..87651e5 100644
--- a/skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js
@@ -21,9 +21,11 @@
 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 @@
                 } 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 @@
                 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 @@
     }
 }
 
-export default Alt.createStore(SkyquakeContainerStore);
+export default Alt.createStore(SkyquakeContainerStore, 'SkyquakeContainerStore');
diff --git a/skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx b/skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx
index f9161cc..d9dff0b 100644
--- a/skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx
+++ b/skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx
@@ -28,7 +28,8 @@
 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;
 
diff --git a/skyquake/framework/widgets/topology/topologyTree.jsx b/skyquake/framework/widgets/topology/topologyTree.jsx
index 5e0d895..9c77c13 100644
--- a/skyquake/framework/widgets/topology/topologyTree.jsx
+++ b/skyquake/framework/widgets/topology/topologyTree.jsx
@@ -20,7 +20,7 @@
 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 @@
             //this.props.selectNode(props.data);
         }
         if(this.svg) {
-          this.update(_.cloneDeep(props.data));
+          this.update(_cloneDeep(props.data));
           // this.selectedID = props.data.id;
         }
     }
diff --git a/skyquake/plugins/about/package.json b/skyquake/plugins/about/package.json
index cb92cf9..46829d5 100644
--- a/skyquake/plugins/about/package.json
+++ b/skyquake/plugins/about/package.json
@@ -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",
diff --git a/skyquake/plugins/about/scripts/build.sh b/skyquake/plugins/about/scripts/build.sh
index 03db9bd..3fd9253 100755
--- a/skyquake/plugins/about/scripts/build.sh
+++ b/skyquake/plugins/about/scripts/build.sh
@@ -27,6 +27,6 @@
 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'
diff --git a/skyquake/plugins/about/src/about.jsx b/skyquake/plugins/about/src/about.jsx
index 6458764..1a43aa4 100644
--- a/skyquake/plugins/about/src/about.jsx
+++ b/skyquake/plugins/about/src/about.jsx
@@ -62,6 +62,23 @@
     // 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 @@
     if (this.state != null) {
       var html = (
               <div className="table-container-wrapper">
+                {fossInfoComponent}
                 {uptimeComponent}
                 <div className="table-container">
                   <h2> Version Info </h2>
diff --git a/skyquake/plugins/about/src/aboutStore.js b/skyquake/plugins/about/src/aboutStore.js
index 934522b..bc94127 100644
--- a/skyquake/plugins/about/src/aboutStore.js
+++ b/skyquake/plugins/about/src/aboutStore.js
@@ -37,5 +37,5 @@
 	console.log('uptime success', time)
 }
 
-module.exports = Alt.createStore(aboutStore);;
+module.exports = Alt.createStore(aboutStore, 'aboutStore');;
 
diff --git a/skyquake/plugins/about/webpack.production.config.js b/skyquake/plugins/about/webpack.production.config.js
index 5be840c..4a2aa12 100644
--- a/skyquake/plugins/about/webpack.production.config.js
+++ b/skyquake/plugins/about/webpack.production.config.js
@@ -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 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 @@
     },
     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;
diff --git a/skyquake/plugins/accounts/package.json b/skyquake/plugins/accounts/package.json
index e126042..d605080 100644
--- a/skyquake/plugins/accounts/package.json
+++ b/skyquake/plugins/accounts/package.json
@@ -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",
diff --git a/skyquake/plugins/accounts/scripts/build.sh b/skyquake/plugins/accounts/scripts/build.sh
index 921cfeb..f8dcff1 100755
--- a/skyquake/plugins/accounts/scripts/build.sh
+++ b/skyquake/plugins/accounts/scripts/build.sh
@@ -27,6 +27,6 @@
 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'
diff --git a/skyquake/plugins/accounts/src/account/account.jsx b/skyquake/plugins/accounts/src/account/account.jsx
index 92c368a..1916ef3 100644
--- a/skyquake/plugins/accounts/src/account/account.jsx
+++ b/skyquake/plugins/accounts/src/account/account.jsx
@@ -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 @@
             }
         }
 
-        let newAccount = _.cloneDeep(removeTrailingWhitespace(Account));
+        let newAccount = _cloneDeep(removeTrailingWhitespace(Account));
         delete newAccount.params;
         newAccount.nestedParams &&
             newAccount.nestedParams['container-name'] &&
@@ -107,8 +107,8 @@
             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 @@
     }
     evaluateSubmit = (e) => {
         if (e.keyCode == 13) {
-            if (this.props.edit) {
+            if (this.props.params.name != 'create') {
                 this.update(e);
             } else {
                 this.create(e);
diff --git a/skyquake/plugins/accounts/src/account/accountSource.js b/skyquake/plugins/accounts/src/account/accountSource.js
index 08fb9f3..45da0fb 100644
--- a/skyquake/plugins/accounts/src/account/accountSource.js
+++ b/skyquake/plugins/accounts/src/account/accountSource.js
@@ -38,7 +38,7 @@
                 return resolve(false);
               }
                $.ajax({
-                url: '/socket-polling',
+                url: '/socket-polling?api_server=' + API_SERVER,
                 type: 'POST',
                 beforeSend: Utils.addAuthorizationStub,
                 data: {
@@ -50,6 +50,7 @@
               }).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 @@
               }).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 @@
               }).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 @@
               }).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');
               });
             })
           },
diff --git a/skyquake/plugins/accounts/webpack.production.config.js b/skyquake/plugins/accounts/webpack.production.config.js
index 3984a58..6356ddb 100644
--- a/skyquake/plugins/accounts/webpack.production.config.js
+++ b/skyquake/plugins/accounts/webpack.production.config.js
@@ -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 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 @@
     },
     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;
diff --git a/skyquake/plugins/composer/api/composer.js b/skyquake/plugins/composer/api/composer.js
index 61cf79b..f2409d6 100644
--- a/skyquake/plugins/composer/api/composer.js
+++ b/skyquake/plugins/composer/api/composer.js
@@ -29,6 +29,7 @@
 
 var Composer = {};
 var FileManager = {};
+var PackageManager = {};
 var DataCenters = {};
 // Catalog module methods
 Composer.get = function(req) {
@@ -304,7 +305,69 @@
     });
 }
 
-Composer.update = function(req) {
+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
+    // package will be hosted.
+    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);
+    }
+
+    var input = {
+        'external-url': download_host + '/composer/upload/' + req.file.filename,
+        'package-type': 'VNFD',
+        'package-id': uuid()
+    }
+
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-create');
+
+    input = utils.addProjectContextToRPCPayload(req, uri, input);
+
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            rp({
+                uri: uri,
+                method: 'POST',
+                headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
+                    'Authorization': req.session && req.session.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']['transaction-id'];
+
+            // Add a status checker on the transaction and then to delete the file later
+            PackageFileHandler.checkCreatePackageStatusAndHandleFile(req, data['transaction_id'], true);
+
+            // Return status to composer UI to update the status.
+            resolve({
+                statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+                data: data
+            });
+        }).catch(function(error) {
+            var res = {};
+            console.log('Problem with PackageManager.upload', error);
+            res.statusCode = error.statusCode || 500;
+            res.errorMessage = {
+                error: 'Failed to upload package ' + req.file.originalname + '. Error: ' + error
+            };
+            reject(res);
+        });
+    });
+};
+
+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.
@@ -319,7 +382,7 @@
         'external-url': download_host + '/composer/update/' + req.file.filename,
         'package-type': 'VNFD',
         'package-id': uuid()
-    }
+    };
 
     var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-update');
 
@@ -365,32 +428,16 @@
     });
 };
 
-Composer.upload = 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'];
-    // dev_download_server is for testing purposes.
-    // It is the direct IP address of the Node server where the
-    // package will be hosted.
-    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);
-    }
-
-    var input = {
-        'external-url': download_host + '/composer/upload/' + req.file.filename,
-        'package-type': 'VNFD',
-        'package-id': uuid()
-    };
-
-    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-create');
-
+    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
@@ -399,37 +446,103 @@
                 rejectUnauthorized: false,
                 resolveWithFullResponse: true,
                 json: true,
-                body: {
-                    input: input
-                }
+                body: { "input": input }
             })
         ]).then(function(result) {
             var data = {};
-            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']);
-
-            // Return status to composer UI to update the status.
             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);
         });
     });
-};
+}
 
+PackageManager.copy = function(req) {
+    // /api/operations/package-copy
+    var api_server = req.query['api_server'];
+    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-copy');
+    var input = req.body;
+    input = utils.addProjectContextToRPCPayload(req, uri, input);
 
+    return new Promise(function(resolve, reject) {
+        Promise.all([
+            rp({
+                uri: 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 = {};
+            resolve({
+                statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+                data: result[0].body
+            });
+        }).catch(function(error) {
+            var res = {};
+            console.log('Problem with PackageManager.copy', error);
+            res.statusCode = error.statusCode || 500;
+            res.errorMessage = {
+                error: error
+            };
+            reject(res);
+        });
+    });
+}
 
-Composer.addFile = 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'];
@@ -444,19 +557,20 @@
         '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.session && req.session.authorization
+                    'Authorization': req.get('Authorization')
                 }),
                 forever: constants.FOREVER_ON,
                 rejectUnauthorized: false,
@@ -485,43 +599,6 @@
     });
 }
 
-Composer.exportPackage = function(req) {
-    var api_server = req.query['api_server'];
-    var uri = utils.projectContextUrl(req, utils.confdPort(api_server) + '/api/operations/package-export');
-    var input = req.body;
-    input = utils.addProjectContextToRPCPayload(req, uri, input);
-    return new Promise(function(resolve, reject) {
-        Promise.all([
-            rp({
-                uri: uri,
-                method: 'POST',
-                headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
-                    'Authorization': req.session && req.session.authorization
-                }),
-                forever: constants.FOREVER_ON,
-                rejectUnauthorized: false,
-                resolveWithFullResponse: true,
-                json: true,
-                body: { "input": input }
-            })
-        ]).then(function(result) {
-            var data = {};
-            resolve({
-                statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
-                data: result[0].body
-            });
-        }).catch(function(error) {
-            var res = {};
-            console.log('Problem with Composer.exportPackage', error);
-            res.statusCode = error.statusCode || 500;
-            res.errorMessage = {
-                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 @@
 }
 module.exports = {
     Composer:Composer,
-    FileManager: FileManager
+    FileManager: FileManager,
+    PackageManager: PackageManager
 };
diff --git a/skyquake/plugins/composer/package.json b/skyquake/plugins/composer/package.json
index 6a01af1..5b45239 100644
--- a/skyquake/plugins/composer/package.json
+++ b/skyquake/plugins/composer/package.json
@@ -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",
diff --git a/skyquake/plugins/composer/routes.js b/skyquake/plugins/composer/routes.js
index 3782209..b3641aa 100644
--- a/skyquake/plugins/composer/routes.js
+++ b/skyquake/plugins/composer/routes.js
@@ -23,6 +23,7 @@
 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,28 +104,9 @@
         res.send(error.errorMessage);
     });
 });
-router.post('/upload', cors(), upload.single('package'), function (req, res, next) {
-    Composer.upload(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) {
-        utils.sendSuccessResponse(data, res);
-    }, function(error) {
-        utils.sendErrorResponse(error, res);
-    });
-});
-router.use('/update', cors(), express.static('upload/packages'));
-
-
 
 router.post('/api/file-manager', cors(), upload.single('package'), function (req, res, next) {
-    Composer.addFile(req).then(function(data) {
+    FileManager.addFile(req).then(function(data) {
         utils.sendSuccessResponse(data, res);
     }, function(error) {
         utils.sendErrorResponse(error, res);
@@ -153,8 +135,42 @@
     });
 });
 
+// Catalog operations via package manager
+
+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.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.use('/update', cors(), express.static('upload/packages'));
+
 router.post('/api/package-export', cors(), function (req, res, next) {
-    Composer.exportPackage(req).then(function(data) {
+    PackageManager.export(req).then(function(data) {
+        utils.sendSuccessResponse(data, res);
+    }, function(error) {
+        utils.sendErrorResponse(error, res);
+    });
+});
+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.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);
diff --git a/skyquake/plugins/composer/scripts/build.sh b/skyquake/plugins/composer/scripts/build.sh
index eb17e4e..ae23fac 100755
--- a/skyquake/plugins/composer/scripts/build.sh
+++ b/skyquake/plugins/composer/scripts/build.sh
@@ -27,6 +27,6 @@
 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'
diff --git a/skyquake/plugins/composer/src/src/actions/CatalogPackageManagerActions.js b/skyquake/plugins/composer/src/src/actions/CatalogPackageManagerActions.js
index 22e32d3..2769c33 100644
--- a/skyquake/plugins/composer/src/src/actions/CatalogPackageManagerActions.js
+++ b/skyquake/plugins/composer/src/src/actions/CatalogPackageManagerActions.js
@@ -21,7 +21,17 @@
 class CatalogPackageManagerActions {
 
 	constructor() {
-		this.generateActions('downloadCatalogPackage', 'downloadCatalogPackageStatusUpdated', 'downloadCatalogPackageError', 'uploadCatalogPackage', 'uploadCatalogPackageStatusUpdated', 'uploadCatalogPackageError', 'removeCatalogPackage');
+		this.generateActions(
+			'downloadCatalogPackage', 
+			'downloadCatalogPackageStatusUpdated',
+			'downloadCatalogPackageError', 
+			'uploadCatalogPackage', 
+			'uploadCatalogPackageStatusUpdated', 
+			'uploadCatalogPackageError',
+			'copyCatalogPackage', 
+			'updateStatus', 
+			'removeCatalogOperation'
+			);
 	}
 
 }
diff --git a/skyquake/plugins/composer/src/src/components/CanvasPanel.js b/skyquake/plugins/composer/src/src/components/CanvasPanel.js
index 9eece49..160db5f 100644
--- a/skyquake/plugins/composer/src/src/components/CanvasPanel.js
+++ b/skyquake/plugins/composer/src/src/components/CanvasPanel.js
@@ -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 @@
 		);
 	},
 	onDragOver(event) {
-		const isDraggingFiles = _.includes(event.dataTransfer.types, 'Files');
+		const isDraggingFiles = _includes(event.dataTransfer.types, 'Files');
 		if (!isDraggingFiles) {
 			event.preventDefault();
 			event.dataTransfer.dropEffect = 'copy';
diff --git a/skyquake/plugins/composer/src/src/components/CatalogPackageManager.js b/skyquake/plugins/composer/src/src/components/CatalogPackageManager.js
index c0b996c..0811093 100644
--- a/skyquake/plugins/composer/src/src/components/CatalogPackageManager.js
+++ b/skyquake/plugins/composer/src/src/components/CatalogPackageManager.js
@@ -88,7 +88,7 @@
 
 		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 packages = this.state.packages || [];
+		const operations = this.state.operations || [];
 		return (
 			<div className="CatalogPackageManager">
 				<div className="items">
-					{packages.map(createItem)}
+					{operations.map(createItem)}
 				</div>
 			</div>
 		);
diff --git a/skyquake/plugins/composer/src/src/components/CatalogPanel.js b/skyquake/plugins/composer/src/src/components/CatalogPanel.js
index a3574e7..6d58c8a 100644
--- a/skyquake/plugins/composer/src/src/components/CatalogPanel.js
+++ b/skyquake/plugins/composer/src/src/components/CatalogPanel.js
@@ -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 @@
 			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();
diff --git a/skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js b/skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js
index 896badd..bc8db58 100644
--- a/skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js
+++ b/skyquake/plugins/composer/src/src/components/CatalogPanelToolbar.js
@@ -102,6 +102,7 @@
 		CatalogItemsActions.createCatalogItem(type);
 	},
 	onClickDuplicateCatalogItem() {
+		CatalogPanelTrayActions.open();
 		CatalogItemsActions.duplicateSelectedCatalogItem();
 	},
 	onClickExportCatalogItems() {
diff --git a/skyquake/plugins/composer/src/src/components/DetailsPanel.js b/skyquake/plugins/composer/src/src/components/DetailsPanel.js
index 37fc17b..e13282e 100644
--- a/skyquake/plugins/composer/src/src/components/DetailsPanel.js
+++ b/skyquake/plugins/composer/src/src/components/DetailsPanel.js
@@ -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 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';
diff --git a/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js b/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js
index 65712f4..22ba179 100644
--- a/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js
+++ b/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js
@@ -20,9 +20,11 @@
  *
  * 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 '../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 @@
 				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 @@
 		);
 	}
 
-	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 @@
 				// 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 @@
 
 	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 @@
 					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 @@
 					utils.assignPathValue(this.model, [value].join('.'), newChoiceObject)
 				}
 
-
 				// update the selected name
 				utils.assignPathValue(this.model, statePath.concat('selected').join('.'), value);
 
@@ -373,13 +469,20 @@
 			}
 		}
 
+		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 @@
 			);
 		});
 
-		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 @@
 					{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 @@
 
 	}
 
-	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 @@
 		);
 	}
 
-	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 @@
 		);
 	}
 
-	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 @@
 		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 @@
 				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 @@
 		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 @@
 			}
 
 			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 @@
 				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 @@
 			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 @@
 		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 @@
 			{buildAdvancedGroup()}
 		</div>
 	);
+};
 
-}
diff --git a/skyquake/plugins/composer/src/src/components/filemanager/FileManager.jsx b/skyquake/plugins/composer/src/src/components/filemanager/FileManager.jsx
index 3833ab4..ed9ea93 100644
--- a/skyquake/plugins/composer/src/src/components/filemanager/FileManager.jsx
+++ b/skyquake/plugins/composer/src/src/components/filemanager/FileManager.jsx
@@ -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 @@
             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 @@
 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 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 @@
     }
     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>
         )
diff --git a/skyquake/plugins/composer/src/src/components/filemanager/FileManagerSource.js b/skyquake/plugins/composer/src/src/components/filemanager/FileManagerSource.js
index 325d026..1f0fd80 100644
--- a/skyquake/plugins/composer/src/src/components/filemanager/FileManagerSource.js
+++ b/skyquake/plugins/composer/src/src/components/filemanager/FileManagerSource.js
@@ -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 @@
                         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 @@
                 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 @@
                 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);
diff --git a/skyquake/plugins/composer/src/src/libraries/DeletionManager.js b/skyquake/plugins/composer/src/src/libraries/DeletionManager.js
index 026555c..d5734df 100644
--- a/skyquake/plugins/composer/src/src/libraries/DeletionManager.js
+++ b/skyquake/plugins/composer/src/src/libraries/DeletionManager.js
@@ -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'
diff --git a/skyquake/plugins/composer/src/src/libraries/SelectionManager.js b/skyquake/plugins/composer/src/src/libraries/SelectionManager.js
index 94ab813..3089b82 100644
--- a/skyquake/plugins/composer/src/src/libraries/SelectionManager.js
+++ b/skyquake/plugins/composer/src/src/libraries/SelectionManager.js
@@ -5,7 +5,6 @@
 
 'use strict';
 
-import _ from 'lodash'
 import d3 from 'd3'
 import UID from './UniqueId'
 import React from 'react'
diff --git a/skyquake/plugins/composer/src/src/libraries/graph/DescriptorGraph.js b/skyquake/plugins/composer/src/src/libraries/graph/DescriptorGraph.js
index b70c921..69ae493 100644
--- a/skyquake/plugins/composer/src/src/libraries/graph/DescriptorGraph.js
+++ b/skyquake/plugins/composer/src/src/libraries/graph/DescriptorGraph.js
@@ -21,7 +21,6 @@
  */
 'use strict';
 
-import _ from 'lodash'
 import d3 from 'd3'
 import math from './math'
 import ClassNames from 'classnames'
diff --git a/skyquake/plugins/composer/src/src/libraries/graph/HighlightRecordServicePaths.js b/skyquake/plugins/composer/src/src/libraries/graph/HighlightRecordServicePaths.js
index 9274c23..0094e01 100644
--- a/skyquake/plugins/composer/src/src/libraries/graph/HighlightRecordServicePaths.js
+++ b/skyquake/plugins/composer/src/src/libraries/graph/HighlightRecordServicePaths.js
@@ -4,7 +4,7 @@
 
 'use strict';
 
-import _ from 'lodash'
+import _isArray from 'lodash/isArray'
 import d3 from 'd3'
 
 /**
@@ -26,7 +26,7 @@
 		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);
diff --git a/skyquake/plugins/composer/src/src/libraries/graph/layouts/RelationsAndNetworksLayout.js b/skyquake/plugins/composer/src/src/libraries/graph/layouts/RelationsAndNetworksLayout.js
index f53b4dc..571fcdf 100644
--- a/skyquake/plugins/composer/src/src/libraries/graph/layouts/RelationsAndNetworksLayout.js
+++ b/skyquake/plugins/composer/src/src/libraries/graph/layouts/RelationsAndNetworksLayout.js
@@ -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'
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModel.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModel.js
index 1a2ba4f..eb70e2a 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModel.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModel.js
@@ -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 @@
 
 	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;
 		}
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFactory.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFactory.js
index ac59872..6de5d21 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFactory.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFactory.js
@@ -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 @@
 		}
 
 		return (containerList, obj) => {
-			if (_.isEmpty(obj)) {
+			if (_isEmpty(obj)) {
 				return containerList;
 			}
 			switch (obj.uiState.type) {
@@ -249,7 +250,7 @@
 		} else {
 			model = vnfdToWrap;
 		}
-		return new VirtualNetworkFunctionReadOnlyWrapper(_.cloneDeep(model), parent);
+		return new VirtualNetworkFunctionReadOnlyWrapper(_cloneDeep(model), parent);
 	}
 
 	static newClassifier(model, parent) {
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaFactory.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaFactory.js
index d3ef200..69098ec 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaFactory.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaFactory.js
@@ -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 @@
 	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 @@
 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 @@
 
 		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 @@
 	},
 	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
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaProperty.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaProperty.js
index 8fe51d9..e064457 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaProperty.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaProperty.js
@@ -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 @@
 	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 @@
         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 @@
 		}
 		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 @@
 				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 @@
 			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);
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelSerializer.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelSerializer.js
index 8ce90cf..b496041 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelSerializer.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelSerializer.js
@@ -1,4 +1,3 @@
-
 /*
  *
  *   Copyright 2016 RIFT.IO Inc
@@ -20,223 +19,28 @@
  * 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;
+		if (!model.uiState) {
+			console.error('model uiState null', model);
+			return {};
 		}
-		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);
-		}
+		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
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorTemplateFactory.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorTemplateFactory.js
index 28e7480..2946f18 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorTemplateFactory.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorTemplateFactory.js
@@ -23,7 +23,10 @@
 
 '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 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 @@
 	createModelForType(type) {
 		const template = DescriptorTemplates[type];
 		if (template) {
-			const model = _.cloneDeep(template);
+			const model = _cloneDeep(template);
 			return resolveInitHandlers(model);
 		}
 	}
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorTemplates.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorTemplates.js
index 89e0a87..1183d4e 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorTemplates.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorTemplates.js
@@ -23,25 +23,27 @@
 
 '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 @@
 				'external-interface': [
 					{
 						'name': 'eth0',
-						'vnfd-connection-point-ref': 'cp1',
+						'vnfd-connection-point-ref': 'connection-point-1',
 						'virtual-interface': {
 							'type': 'VIRTIO'
 						}
@@ -62,8 +64,6 @@
 		]
 	},
 	'vnfd.internal-vld': {
-		'id': () => guid(),
-		'name': () => 'vld-' + InstanceCounter.count('new.vnfd.internal-vld'),
 		'description': 'Virtual link for internal fabric',
 		'type': 'ELAN'
 	}
diff --git a/skyquake/plugins/composer/src/src/libraries/model/descriptors/ForwardingGraph.js b/skyquake/plugins/composer/src/src/libraries/model/descriptors/ForwardingGraph.js
index ef99af6..1173296 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/descriptors/ForwardingGraph.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/descriptors/ForwardingGraph.js
@@ -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 @@
 	}
 
 	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);
 	}
 
diff --git a/skyquake/plugins/composer/src/src/libraries/model/descriptors/NetworkService.js b/skyquake/plugins/composer/src/src/libraries/model/descriptors/NetworkService.js
index 16fb159..b10fceb 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/descriptors/NetworkService.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/descriptors/NetworkService.js
@@ -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 @@
 	}
 
 	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 @@
 	}
 
 	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);
 	}
 
diff --git a/skyquake/plugins/composer/src/src/libraries/model/descriptors/RecordServicePath.js b/skyquake/plugins/composer/src/src/libraries/model/descriptors/RecordServicePath.js
index 8c43f10..7c2b631 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/descriptors/RecordServicePath.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/descriptors/RecordServicePath.js
@@ -21,7 +21,6 @@
 
 'use strict';
 
-import _ from 'lodash'
 import DescriptorModel from '../DescriptorModel'
 import RspConnectionPointRef from './RspConnectionPointRef'
 import DescriptorModelFactory from '../DescriptorModelFactory'
diff --git a/skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunction.js b/skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunction.js
index 73e10d3..5f3ad04 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunction.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunction.js
@@ -52,7 +52,9 @@
 	}
 
 	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 @@
 	}
 
 	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);
 	}
 
diff --git a/skyquake/plugins/composer/src/src/libraries/utils.js b/skyquake/plugins/composer/src/src/libraries/utils.js
index ff2adc0..d39a138 100644
--- a/skyquake/plugins/composer/src/src/libraries/utils.js
+++ b/skyquake/plugins/composer/src/src/libraries/utils.js
@@ -18,7 +18,14 @@
 
 '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 @@
 	},
 
 	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 @@
 
 	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 @@
 		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 @@
 					}
 				}
 			} 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 @@
 						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 @@
 								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 @@
 							break;
 						}
 						objectCopy = objectCopy[fragment];
+						if (!objectCopy) {
+							// contains no value
+							break;
+						}
 					}
 				}
 			}
@@ -279,7 +290,7 @@
 	},
 
 	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 @@
 			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 @@
 			} 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) {
-								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;
+									}
+								}
+							}
+						}
+					}
+				}
+			} else if (fieldKeyArray.length == 3) {
+				for (let key in catalogs) {
+					for (let subKey in catalogs[key]) {
+						let found = _find(catalogs[key][subKey], {id: fieldKeyArray[0]});
+						if (found) {
+							for (let foundKey in found) {
+								if (_isArray(found[foundKey])) {
+									let topLevel = _find(found[foundKey], {id: fieldKeyArray[1]});
+									if (topLevel) {
+										results = 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;
+									}
 								}
 							}
 						}
diff --git a/skyquake/plugins/composer/src/src/sources/CatalogDataSource.js b/skyquake/plugins/composer/src/src/sources/CatalogDataSource.js
index e742a9e..9c297ca 100644
--- a/skyquake/plugins/composer/src/src/sources/CatalogDataSource.js
+++ b/skyquake/plugins/composer/src/src/sources/CatalogDataSource.js
@@ -18,7 +18,6 @@
  */
 'use strict';
 
-import _ from 'lodash'
 import $ from 'jquery'
 import alt from '../alt'
 import utils from '../libraries/utils'
diff --git a/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js b/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js
index 48d697b..e059f94 100644
--- a/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js
+++ b/skyquake/plugins/composer/src/src/sources/CatalogPackageManagerSource.js
@@ -18,71 +18,98 @@
  */
 '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 @@
 						"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 @@
 			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 @@
 		}
 	},
 
+	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,
diff --git a/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js b/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js
index ea57627..06d1342 100644
--- a/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js
+++ b/skyquake/plugins/composer/src/src/stores/CatalogDataStore.js
@@ -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 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 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 @@
 	}
 
 	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 @@
 							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 @@
 								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 @@
 							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 @@
 	}
 
 	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 @@
 	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 @@
 				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) => {
-			CatalogPackageManagerActions.downloadCatalogPackage.defer({
-				selectedItems: selectedItems,
-				selectedFormat: this.selectedFormat,
-				selectedGrammar: this.selectedGrammar
-			});
-			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);
+			CatalogPackageManagerActions.downloadCatalogPackage.defer({
+				selectedItems: selectedItems,
+				selectedFormat: 'mano',
+				selectedGrammar: 'osm'
+			});
+			this.resetSelectionState();
 		}
 	}
-
-	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');
diff --git a/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js b/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js
index 3a82114..9d23042 100644
--- a/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js
+++ b/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js
@@ -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 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 @@
 	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 @@
 
 	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 @@
 
 	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 @@
 		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 @@
 
 	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 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');
diff --git a/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js b/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js
index ff24cbb..c677a44 100644
--- a/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js
+++ b/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js
@@ -18,7 +18,14 @@
  */
 '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 @@
 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 @@
 			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 @@
 				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 @@
 		}
 		SelectionManager.select(item);
 		this.updateItem(item);
-		this.openFileManagerSockets(item)
+		if (item) {
+			this.openFileManagerSockets(item);
+		}
 	}
 	catalogItemMetaDataChanged(item) {
 		this.updateItem(item);
@@ -267,7 +277,7 @@
 
 	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 @@
 
 			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 @@
         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 @@
         			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 @@
 	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 @@
 		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 @@
 	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 @@
                 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 @@
 	}
 	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 @@
 			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({
diff --git a/skyquake/plugins/composer/test/spec/libraries/DescriptorModelFactorySpec.js b/skyquake/plugins/composer/test/spec/libraries/DescriptorModelFactorySpec.js
index 66376b9..eff8962 100644
--- a/skyquake/plugins/composer/test/spec/libraries/DescriptorModelFactorySpec.js
+++ b/skyquake/plugins/composer/test/spec/libraries/DescriptorModelFactorySpec.js
@@ -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('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 @@
 			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('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, []);
diff --git a/skyquake/plugins/composer/test/spec/libraries/DescriptorModelSpec.js b/skyquake/plugins/composer/test/spec/libraries/DescriptorModelSpec.js
index 5388780..b65e2e7 100644
--- a/skyquake/plugins/composer/test/spec/libraries/DescriptorModelSpec.js
+++ b/skyquake/plugins/composer/test/spec/libraries/DescriptorModelSpec.js
@@ -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 {
diff --git a/skyquake/plugins/composer/webpack.production.config.js b/skyquake/plugins/composer/webpack.production.config.js
index bf2747a..7839f10 100644
--- a/skyquake/plugins/composer/webpack.production.config.js
+++ b/skyquake/plugins/composer/webpack.production.config.js
@@ -28,14 +28,15 @@
 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 @@
 			'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 @@
 			},
 			{ 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
diff --git a/skyquake/plugins/config/package.json b/skyquake/plugins/config/package.json
index b4de560..ff3212a 100644
--- a/skyquake/plugins/config/package.json
+++ b/skyquake/plugins/config/package.json
@@ -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",
diff --git a/skyquake/plugins/config/scripts/build.sh b/skyquake/plugins/config/scripts/build.sh
index 1758212..4a5f8c6 100755
--- a/skyquake/plugins/config/scripts/build.sh
+++ b/skyquake/plugins/config/scripts/build.sh
@@ -13,6 +13,6 @@
 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'
diff --git a/skyquake/plugins/config/src/dashboard/inputs.jsx b/skyquake/plugins/config/src/dashboard/inputs.jsx
index 457a08e..1636f4c 100644
--- a/skyquake/plugins/config/src/dashboard/inputs.jsx
+++ b/skyquake/plugins/config/src/dashboard/inputs.jsx
@@ -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 @@
             }
         }
 
-        let newAccount = _.cloneDeep(removeTrailingWhitespace(Account));
+        let newAccount = _cloneDeep(removeTrailingWhitespace(Account));
         delete newAccount.params;
         newAccount.nestedParams &&
             newAccount.nestedParams['container-name'] &&
diff --git a/skyquake/plugins/config/webpack.production.config.js b/skyquake/plugins/config/webpack.production.config.js
index 49ad631..f78ff42 100644
--- a/skyquake/plugins/config/webpack.production.config.js
+++ b/skyquake/plugins/config/webpack.production.config.js
@@ -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 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 @@
     },
     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;
diff --git a/skyquake/plugins/debug/package.json b/skyquake/plugins/debug/package.json
index fd2dea9..2395b14 100644
--- a/skyquake/plugins/debug/package.json
+++ b/skyquake/plugins/debug/package.json
@@ -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",
diff --git a/skyquake/plugins/debug/scripts/build.sh b/skyquake/plugins/debug/scripts/build.sh
index 49bc9f0..7bac1bf 100755
--- a/skyquake/plugins/debug/scripts/build.sh
+++ b/skyquake/plugins/debug/scripts/build.sh
@@ -27,6 +27,6 @@
 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'
diff --git a/skyquake/plugins/debug/src/crash.jsx b/skyquake/plugins/debug/src/crash.jsx
index 6c659c3..bed1dbf 100644
--- a/skyquake/plugins/debug/src/crash.jsx
+++ b/skyquake/plugins/debug/src/crash.jsx
@@ -21,114 +21,126 @@
 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);
     }
-    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 => {
+    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;
+    }
+    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
+                                }
 
-                        //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/>)
-                        }
-
-                        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;
diff --git a/skyquake/plugins/debug/src/crashStore.js b/skyquake/plugins/debug/src/crashStore.js
index c5ded18..0b04445 100644
--- a/skyquake/plugins/debug/src/crashStore.js
+++ b/skyquake/plugins/debug/src/crashStore.js
@@ -20,20 +20,31 @@
 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');;
 
diff --git a/skyquake/plugins/debug/webpack.production.config.js b/skyquake/plugins/debug/webpack.production.config.js
index 5be840c..2ef01be 100644
--- a/skyquake/plugins/debug/webpack.production.config.js
+++ b/skyquake/plugins/debug/webpack.production.config.js
@@ -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 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 @@
     },
     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;
diff --git a/skyquake/plugins/goodbyeworld/package.json b/skyquake/plugins/goodbyeworld/package.json
index ebb138c..a6d1bba 100644
--- a/skyquake/plugins/goodbyeworld/package.json
+++ b/skyquake/plugins/goodbyeworld/package.json
@@ -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",
diff --git a/skyquake/plugins/goodbyeworld/scripts/build.sh b/skyquake/plugins/goodbyeworld/scripts/build.sh
index 6dc7f5c..3298c2a 100755
--- a/skyquake/plugins/goodbyeworld/scripts/build.sh
+++ b/skyquake/plugins/goodbyeworld/scripts/build.sh
@@ -27,6 +27,6 @@
 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'
diff --git a/skyquake/plugins/goodbyeworld/webpack.production.config.js b/skyquake/plugins/goodbyeworld/webpack.production.config.js
index e013478..7db6acc 100644
--- a/skyquake/plugins/goodbyeworld/webpack.production.config.js
+++ b/skyquake/plugins/goodbyeworld/webpack.production.config.js
@@ -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 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 @@
     },
     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;
diff --git a/skyquake/plugins/helloworld/package.json b/skyquake/plugins/helloworld/package.json
index c874218..df748a3 100644
--- a/skyquake/plugins/helloworld/package.json
+++ b/skyquake/plugins/helloworld/package.json
@@ -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",
diff --git a/skyquake/plugins/helloworld/scripts/build.sh b/skyquake/plugins/helloworld/scripts/build.sh
index 39913a3..aec9106 100755
--- a/skyquake/plugins/helloworld/scripts/build.sh
+++ b/skyquake/plugins/helloworld/scripts/build.sh
@@ -27,6 +27,6 @@
 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'
diff --git a/skyquake/plugins/helloworld/webpack.production.config.js b/skyquake/plugins/helloworld/webpack.production.config.js
index e5728d0..7db6acc 100644
--- a/skyquake/plugins/helloworld/webpack.production.config.js
+++ b/skyquake/plugins/helloworld/webpack.production.config.js
@@ -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 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 @@
     },
     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;
diff --git a/skyquake/plugins/launchpad/api/launchpad.js b/skyquake/plugins/launchpad/api/launchpad.js
index d1ba614..e82fc7c 100644
--- a/skyquake/plugins/launchpad/api/launchpad.js
+++ b/skyquake/plugins/launchpad/api/launchpad.js
@@ -799,8 +799,14 @@
                 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) {
diff --git a/skyquake/plugins/launchpad/package.json b/skyquake/plugins/launchpad/package.json
index 00b93f6..5353a8d 100644
--- a/skyquake/plugins/launchpad/package.json
+++ b/skyquake/plugins/launchpad/package.json
@@ -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"
   }
 }
diff --git a/skyquake/plugins/launchpad/scripts/build.sh b/skyquake/plugins/launchpad/scripts/build.sh
index 611fcc8..c59d28f 100755
--- a/skyquake/plugins/launchpad/scripts/build.sh
+++ b/skyquake/plugins/launchpad/scripts/build.sh
@@ -27,6 +27,6 @@
 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'
diff --git a/skyquake/plugins/launchpad/src/createStore.js b/skyquake/plugins/launchpad/src/createStore.js
index 517c70c..da79e8a 100644
--- a/skyquake/plugins/launchpad/src/createStore.js
+++ b/skyquake/plugins/launchpad/src/createStore.js
@@ -54,5 +54,5 @@
   });
 };
 
-module.exports = alt.createStore(CreateFleet);
+module.exports = alt.createStore(CreateFleet, 'CreateFleet');
 
diff --git a/skyquake/plugins/launchpad/src/instantiate/instantiateDashboard.jsx b/skyquake/plugins/launchpad/src/instantiate/instantiateDashboard.jsx
index b1d2163..aba84c5 100644
--- a/skyquake/plugins/launchpad/src/instantiate/instantiateDashboard.jsx
+++ b/skyquake/plugins/launchpad/src/instantiate/instantiateDashboard.jsx
@@ -36,7 +36,7 @@
 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() {
diff --git a/skyquake/plugins/launchpad/src/instantiate/instantiateStore.js b/skyquake/plugins/launchpad/src/instantiate/instantiateStore.js
index e779beb..3ec2a80 100644
--- a/skyquake/plugins/launchpad/src/instantiate/instantiateStore.js
+++ b/skyquake/plugins/launchpad/src/instantiate/instantiateStore.js
@@ -20,7 +20,8 @@
 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 @@
             },
             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 @@
 
         // 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 @@
         pnfd: data.pnfd
     });
 }
-// export default Alt.createStore(LaunchNetworkServiceStore);
 export default LaunchNetworkServiceStore;
diff --git a/skyquake/plugins/launchpad/src/launchpad.jsx b/skyquake/plugins/launchpad/src/launchpad.jsx
index c012309..88dd1bb 100644
--- a/skyquake/plugins/launchpad/src/launchpad.jsx
+++ b/skyquake/plugins/launchpad/src/launchpad.jsx
@@ -25,7 +25,6 @@
 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';
diff --git a/skyquake/plugins/launchpad/src/launchpad.scss b/skyquake/plugins/launchpad/src/launchpad.scss
index b871f51..6de0e3b 100644
--- a/skyquake/plugins/launchpad/src/launchpad.scss
+++ b/skyquake/plugins/launchpad/src/launchpad.scss
@@ -130,8 +130,8 @@
     }
 
     &-body {
-      -ms-flex-flow: col wrap;
-          flex-flow: col wrap;
+      -ms-flex-flow: column wrap;
+          flex-flow: column wrap;
     }
 
     .nsrSummary {
diff --git a/skyquake/plugins/launchpad/src/launchpadFleetSource.js b/skyquake/plugins/launchpad/src/launchpadFleetSource.js
index cde7116..fff6fae 100644
--- a/skyquake/plugins/launchpad/src/launchpadFleetSource.js
+++ b/skyquake/plugins/launchpad/src/launchpadFleetSource.js
@@ -106,7 +106,7 @@
             return resolve(false);
           }
            $.ajax({
-            url: '/socket-polling',
+            url: '/socket-polling?api_server=' + API_SERVER,
             type: 'POST',
             beforeSend: Utils.addAuthorizationStub,
             data: {
diff --git a/skyquake/plugins/launchpad/src/launchpadFleetStore.js b/skyquake/plugins/launchpad/src/launchpadFleetStore.js
index 520eebe..69255a4 100644
--- a/skyquake/plugins/launchpad/src/launchpadFleetStore.js
+++ b/skyquake/plugins/launchpad/src/launchpadFleetStore.js
@@ -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 @@
 
 
 var FleetStore;
-var _ = require('underscore');
-//  _.debounce(function(){});
 function FleetStoreConstructor() {
   var self = this;
   this.fleets = [];
@@ -134,7 +134,7 @@
       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 @@
       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.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 @@
   data['console-url'] && window.open(data['console-url']);
 }
 
-FleetStore = Alt.createStore(FleetStoreConstructor);
+FleetStore = Alt.createStore(FleetStoreConstructor, 'FleetStore');
 module.exports = FleetStore;
diff --git a/skyquake/plugins/launchpad/src/launchpad_card/jobListCard.jsx b/skyquake/plugins/launchpad/src/launchpad_card/jobListCard.jsx
index 2a66540..5cdcb85 100644
--- a/skyquake/plugins/launchpad/src/launchpad_card/jobListCard.jsx
+++ b/skyquake/plugins/launchpad/src/launchpad_card/jobListCard.jsx
@@ -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 @@
     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>
diff --git a/skyquake/plugins/launchpad/src/launchpad_card/jobListCard.scss b/skyquake/plugins/launchpad/src/launchpad_card/jobListCard.scss
index 8551abb..0ea403f 100644
--- a/skyquake/plugins/launchpad/src/launchpad_card/jobListCard.scss
+++ b/skyquake/plugins/launchpad/src/launchpad_card/jobListCard.scss
@@ -67,6 +67,26 @@
     }
     &--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;
diff --git a/skyquake/plugins/launchpad/src/launchpad_card/nsrConfigPrimitives.jsx b/skyquake/plugins/launchpad/src/launchpad_card/nsrConfigPrimitives.jsx
index b44b3d9..9d0cdba 100644
--- a/skyquake/plugins/launchpad/src/launchpad_card/nsrConfigPrimitives.jsx
+++ b/skyquake/plugins/launchpad/src/launchpad_card/nsrConfigPrimitives.jsx
@@ -43,7 +43,7 @@
                 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 = {}
 
diff --git a/skyquake/plugins/launchpad/src/launchpad_card/nsrScalingGroups.jsx b/skyquake/plugins/launchpad/src/launchpad_card/nsrScalingGroups.jsx
index ac54e7f..db94ded 100644
--- a/skyquake/plugins/launchpad/src/launchpad_card/nsrScalingGroups.jsx
+++ b/skyquake/plugins/launchpad/src/launchpad_card/nsrScalingGroups.jsx
@@ -17,19 +17,24 @@
  */
 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 @@
 
 			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 SkyquakeComponent(NsrScalingGroups);
diff --git a/skyquake/plugins/launchpad/src/recordViewer/recordCard.jsx b/skyquake/plugins/launchpad/src/recordViewer/recordCard.jsx
index 7b3cb21..7fad6f4 100644
--- a/skyquake/plugins/launchpad/src/recordViewer/recordCard.jsx
+++ b/skyquake/plugins/launchpad/src/recordViewer/recordCard.jsx
@@ -32,7 +32,7 @@
 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 @@
                 //     </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 @@
       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(
diff --git a/skyquake/plugins/launchpad/src/recordViewer/recordViewSource.js b/skyquake/plugins/launchpad/src/recordViewer/recordViewSource.js
index a3b2d67..49c3122 100644
--- a/skyquake/plugins/launchpad/src/recordViewer/recordViewSource.js
+++ b/skyquake/plugins/launchpad/src/recordViewer/recordViewSource.js
@@ -94,7 +94,7 @@
                 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 @@
                 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 @@
                 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: {
diff --git a/skyquake/plugins/launchpad/src/recordViewer/recordViewStore.js b/skyquake/plugins/launchpad/src/recordViewer/recordViewStore.js
index a7770a7..72609e4 100644
--- a/skyquake/plugins/launchpad/src/recordViewer/recordViewStore.js
+++ b/skyquake/plugins/launchpad/src/recordViewer/recordViewStore.js
@@ -20,7 +20,9 @@
 // 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 @@
 
                     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 @@
 
             // 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 @@
             };
         }
 
-        navigatorState = _.extend(navigatorState, {
+        navigatorState = _extend(navigatorState, {
             recordData: recordData,
             recordType: type,
             cardLoading: false,
@@ -472,4 +474,4 @@
     };
 }
 
-export default Alt.createStore(RecordViewStore);
+export default Alt.createStore(RecordViewStore, 'RecordViewStore');
diff --git a/skyquake/plugins/launchpad/src/ssh_keys/sshKeyStore.js b/skyquake/plugins/launchpad/src/ssh_keys/sshKeyStore.js
index 7ae421b..c62a1b4 100644
--- a/skyquake/plugins/launchpad/src/ssh_keys/sshKeyStore.js
+++ b/skyquake/plugins/launchpad/src/ssh_keys/sshKeyStore.js
@@ -20,7 +20,8 @@
 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 @@
             keys: [],
             entities: {}
         };
-        this.dataCache = _.cloneDeep(this.data);
+        this.dataCache = _cloneDeep(this.data);
         this.newKey = {
           name: '',
           key: ''
@@ -67,7 +68,7 @@
     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 @@
             isEditable: false
         };
         this.setState({
-            dataCache: _.cloneDeep(keys),
+            dataCache: _cloneDeep(keys),
             data: keys,
             newKey: {
               name: '',
@@ -122,7 +123,7 @@
             isEditable: false
         };
         this.setState({
-            dataCache: _.cloneDeep(keys),
+            dataCache: _cloneDeep(keys),
             data: keys,
             newKey: {
               name: '',
@@ -135,7 +136,7 @@
         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 @@
         let flattened = this.flattenKeys(data);
         this.setState({
             data: flattened,
-            dataCache: _.cloneDeep(flattened)
+            dataCache: _cloneDeep(flattened)
         })
     }
     updateSshKeyPair = (k, field) => {
@@ -166,7 +167,7 @@
         };
         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;
     }
diff --git a/skyquake/plugins/launchpad/src/ssh_keys/sshKeys.jsx b/skyquake/plugins/launchpad/src/ssh_keys/sshKeys.jsx
index f832215..3d4179e 100644
--- a/skyquake/plugins/launchpad/src/ssh_keys/sshKeys.jsx
+++ b/skyquake/plugins/launchpad/src/ssh_keys/sshKeys.jsx
@@ -27,7 +27,7 @@
 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);
     }
diff --git a/skyquake/plugins/launchpad/src/topologyL2View/detailView.jsx b/skyquake/plugins/launchpad/src/topologyL2View/detailView.jsx
index 9436671..459f4af 100644
--- a/skyquake/plugins/launchpad/src/topologyL2View/detailView.jsx
+++ b/skyquake/plugins/launchpad/src/topologyL2View/detailView.jsx
@@ -20,7 +20,7 @@
 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 @@
     }
 
     detailData(data) {
-        if (_.isEmpty(data)) {
+        if (_isEmpty(data)) {
             return {};
         } else {
             return {
diff --git a/skyquake/plugins/launchpad/src/topologyL2View/topologyL2Source.js b/skyquake/plugins/launchpad/src/topologyL2View/topologyL2Source.js
index fd38d84..756c775 100644
--- a/skyquake/plugins/launchpad/src/topologyL2View/topologyL2Source.js
+++ b/skyquake/plugins/launchpad/src/topologyL2View/topologyL2Source.js
@@ -41,7 +41,7 @@
             return resolve(false);
           }
            $.ajax({
-            url: '/socket-polling',
+            url: '/socket-polling?api_server=' + API_SERVER,
             type: 'POST',
             beforeSend: Utils.addAuthorizationStub,
             data: {
diff --git a/skyquake/plugins/launchpad/src/topologyL2View/topologyL2Store.js b/skyquake/plugins/launchpad/src/topologyL2View/topologyL2Store.js
index 2d14952..b6bed73 100644
--- a/skyquake/plugins/launchpad/src/topologyL2View/topologyL2Store.js
+++ b/skyquake/plugins/launchpad/src/topologyL2View/topologyL2Store.js
@@ -132,4 +132,4 @@
         });
     }
 }
-    export default Alt.createStore(TopologyL2Store);
+    export default Alt.createStore(TopologyL2Store, 'TopologyL2Store');
diff --git a/skyquake/plugins/launchpad/src/topologyView/topologySource.js b/skyquake/plugins/launchpad/src/topologyView/topologySource.js
index 04e1c66..b570082 100644
--- a/skyquake/plugins/launchpad/src/topologyView/topologySource.js
+++ b/skyquake/plugins/launchpad/src/topologyView/topologySource.js
@@ -42,7 +42,7 @@
             return resolve(false);
           }
            $.ajax({
-            url: '/socket-polling',
+            url: '/socket-polling?api_server=' + API_SERVER,
             type: 'POST',
             beforeSend: Utils.addAuthorizationStub,
             data: {
diff --git a/skyquake/plugins/launchpad/src/topologyView/topologyStore.js b/skyquake/plugins/launchpad/src/topologyView/topologyStore.js
index 10c5005..5d6cef9 100644
--- a/skyquake/plugins/launchpad/src/topologyView/topologyStore.js
+++ b/skyquake/plugins/launchpad/src/topologyView/topologyStore.js
@@ -135,4 +135,4 @@
     }
 
 }
-export default Alt.createStore(TopologyStore);
+export default Alt.createStore(TopologyStore, 'TopologyStore');
diff --git a/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkCreate.jsx b/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkCreate.jsx
index d3dc011..bec414e 100644
--- a/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkCreate.jsx
+++ b/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkCreate.jsx
@@ -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 @@
 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);
 	}
diff --git a/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkCreateStore.js b/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkCreateStore.js
index dbd7f58..ac050e4 100644
--- a/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkCreateStore.js
+++ b/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkCreateStore.js
@@ -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 @@
 	}
 
 	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 @@
 	}
 
 	updateSecondLevelKey = (firstLevelKey, secondLevelKey, e) => {
-		let vld = _.cloneDeep(this.vld);
+		let vld = _cloneDeep(this.vld);
 		if (!vld[firstLevelKey]) {
 			vld[firstLevelKey] = {};
 		}
@@ -176,7 +178,7 @@
 	}
 
 	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 @@
 	}
 
 	updateFirstLevelListKeyChange = (listName, index, keyName, e) => {
-		let vld = _.cloneDeep(this.vld);
+		let vld = _cloneDeep(this.vld);
 		
 
 		!vld[listName] && (vld[listName] = []);
@@ -207,7 +209,7 @@
 	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 @@
 	}
 
 	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 @@
 
 	cleanupPayload = (mode, vld) => {
 		// Do necessary cleanup here
-		let cleanVld = _.pickBy(vld, _.identity);
+		let cleanVld = _pickBy(vld, _identity);
 		return cleanVld;
 	}
 
diff --git a/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkDetails.jsx b/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkDetails.jsx
index 33404e0..c6a45e7 100644
--- a/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkDetails.jsx
+++ b/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinkDetails.jsx
@@ -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 @@
 				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(
diff --git a/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinks.jsx b/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinks.jsx
index fa09d9f..37cf0b3 100644
--- a/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinks.jsx
+++ b/skyquake/plugins/launchpad/src/virtual_links/nsVirtualLinks.jsx
@@ -19,7 +19,7 @@
 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 @@
 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 @@
 	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 @@
 		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})
 		});
 	}
 
diff --git a/skyquake/plugins/launchpad/src/vnfr/vnfrSource.js b/skyquake/plugins/launchpad/src/vnfr/vnfrSource.js
index 782f322..327bdb9 100644
--- a/skyquake/plugins/launchpad/src/vnfr/vnfrSource.js
+++ b/skyquake/plugins/launchpad/src/vnfr/vnfrSource.js
@@ -42,7 +42,7 @@
           }
           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: {
diff --git a/skyquake/plugins/launchpad/src/vnfr/vnfrStore.js b/skyquake/plugins/launchpad/src/vnfr/vnfrStore.js
index 9a64531..5e09d7a 100644
--- a/skyquake/plugins/launchpad/src/vnfr/vnfrStore.js
+++ b/skyquake/plugins/launchpad/src/vnfr/vnfrStore.js
@@ -63,4 +63,4 @@
   }
 };
 
-export default alt.createStore(VnfrStore)
+export default alt.createStore(VnfrStore, 'VnfrStore')
diff --git a/skyquake/plugins/launchpad/webpack.production.config.js b/skyquake/plugins/launchpad/webpack.production.config.js
index 3984a58..ed2a23f 100644
--- a/skyquake/plugins/launchpad/webpack.production.config.js
+++ b/skyquake/plugins/launchpad/webpack.production.config.js
@@ -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 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 @@
     },
     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;
diff --git a/skyquake/plugins/logging/package.json b/skyquake/plugins/logging/package.json
index ae4ef97..d58e262 100644
--- a/skyquake/plugins/logging/package.json
+++ b/skyquake/plugins/logging/package.json
@@ -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",
diff --git a/skyquake/plugins/logging/scripts/build.sh b/skyquake/plugins/logging/scripts/build.sh
index 4b23c01..569bc06 100755
--- a/skyquake/plugins/logging/scripts/build.sh
+++ b/skyquake/plugins/logging/scripts/build.sh
@@ -27,6 +27,6 @@
 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'
diff --git a/skyquake/plugins/logging/src/loggingGeneral.jsx b/skyquake/plugins/logging/src/loggingGeneral.jsx
index 7b50c76..0aced4e 100644
--- a/skyquake/plugins/logging/src/loggingGeneral.jsx
+++ b/skyquake/plugins/logging/src/loggingGeneral.jsx
@@ -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 @@
   getData() {
     LoggingStore.getLoggingConfig();
     this.setState({
-      isLoading: _.isEmpty(this.state.loggingConfig)
+      isLoading: _isEmpty(this.state.loggingConfig)
     });
   }
   componentWillUnmount = () => {
@@ -239,7 +241,7 @@
     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 @@
   //   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 @@
     // 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,
diff --git a/skyquake/plugins/logging/src/loggingStore.js b/skyquake/plugins/logging/src/loggingStore.js
index 6743b92..3c306ae 100644
--- a/skyquake/plugins/logging/src/loggingStore.js
+++ b/skyquake/plugins/logging/src/loggingStore.js
@@ -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 @@
   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 @@
 
   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 @@
   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 @@
     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 @@
     // 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 @@
       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 @@
                 // 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 @@
   }
 }
 
-export default alt.createStore(LoggingStore);
+export default alt.createStore(LoggingStore, 'LoggingStore');
diff --git a/skyquake/plugins/logging/webpack.production.config.js b/skyquake/plugins/logging/webpack.production.config.js
index 87e91c7..a4e4834 100644
--- a/skyquake/plugins/logging/webpack.production.config.js
+++ b/skyquake/plugins/logging/webpack.production.config.js
@@ -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 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 @@
     },
     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;
diff --git a/skyquake/scripts/build.sh b/skyquake/scripts/build.sh
index 044268d..9d9d635 100755
--- a/skyquake/scripts/build.sh
+++ b/skyquake/scripts/build.sh
@@ -38,7 +38,7 @@
         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'
diff --git a/skyquake/skyquake.js b/skyquake/skyquake.js
index 2396e1a..6019357 100644
--- a/skyquake/skyquake.js
+++ b/skyquake/skyquake.js
@@ -272,6 +272,13 @@
 		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();
+	    });
+
 	}
 
 	/**
diff --git a/skyquake/tests/stories/catalogCard.js b/skyquake/tests/stories/catalogCard.js
index b7e0e57..143dfe8 100644
--- a/skyquake/tests/stories/catalogCard.js
+++ b/skyquake/tests/stories/catalogCard.js
@@ -11,7 +11,7 @@
 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 = {
diff --git a/skyquake/tests/stories/sshKeyCard.js b/skyquake/tests/stories/sshKeyCard.js
index 649ec4a..dcaf417 100644
--- a/skyquake/tests/stories/sshKeyCard.js
+++ b/skyquake/tests/stories/sshKeyCard.js
@@ -11,7 +11,7 @@
 // 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 />))