RIFT-15154: Config parameter map

Signed-off-by: Laurence Maultsby <laurence.maultsby@riftio.com>
diff --git a/skyquake/plugins/composer/src/src/components/CanvasPanel.js b/skyquake/plugins/composer/src/src/components/CanvasPanel.js
index 96904ea..dda1372 100644
--- a/skyquake/plugins/composer/src/src/components/CanvasPanel.js
+++ b/skyquake/plugins/composer/src/src/components/CanvasPanel.js
@@ -34,9 +34,9 @@
 import EditForwardingGraphPaths from './EditorForwardingGraph/EditForwardingGraphPaths'
 import SelectionManager from '../libraries/SelectionManager'
 import DescriptorModelIconFactory from '../libraries/model/IconFactory'
-
 import FileManager from './filemanager/FileManager.jsx';
 
+import ConfigPrimitiveParameters from './ConfigPrimitiveParameters/ConfigPrimitiveParameters'
 import '../styles/CanvasPanel.scss'
 
 const CanvasPanel = React.createClass({
@@ -87,6 +87,13 @@
 					</div>
 				</div>
        		)
+        //CanvasPanelTray panel to display
+        let displayedPanel = null;
+        switch (this.props.displayedPanel) {
+            case 'forwarding' : displayedPanel = (<EditForwardingGraphPaths containers={this.props.containers} />); break;
+            case 'parameter' : displayedPanel = (<ConfigPrimitiveParameters  containers={this.props.containers} />); break;
+            default: displayedPanel = (<div><p className="welcome-message">Please select a tab</p></div>); break;
+        }
 		return (
 			<div id="canvasPanelDiv" className="CanvasPanel" style={style} onDragOver={this.onDragOver} onDrop={this.onDrop}>
 				<div onDoubleClick={this.onDblClickOpenFullScreen}  className="CanvasPanelHeader panel-header" data-resizable="limit_bottom">
@@ -104,8 +111,8 @@
 						<CanvasZoom zoom={this.props.zoom} style={{bottom: this.props.layout.bottom + 20}}/>
 						: null
 				}
-				<CanvasPanelTray layout={this.props.layout} show={isEditingNSD && isDescriptorView}>
-					<EditForwardingGraphPaths containers={this.props.containers} />
+				<CanvasPanelTray layout={this.props.layout} displayedPanel={this.props.displayedPanel} show={isEditingNSD && isDescriptorView}>
+					{displayedPanel}
 				</CanvasPanelTray>
 			</div>
 		);
diff --git a/skyquake/plugins/composer/src/src/components/CanvasPanelTray.js b/skyquake/plugins/composer/src/src/components/CanvasPanelTray.js
index 57f29a0..5fde578 100644
--- a/skyquake/plugins/composer/src/src/components/CanvasPanelTray.js
+++ b/skyquake/plugins/composer/src/src/components/CanvasPanelTray.js
@@ -16,13 +16,17 @@
 		right: props.layout.right,
 		display: props.show ? false : 'none'
 	};
+    const PANEL = {
+        FORWARD: 'forwarding',
+        PARAMETER: 'parameter'
+    }
 	const classNames = ClassNames('CanvasPanelTray', {'-with-transitions': !document.body.classList.contains('resizing')});
 	function onClickToggleOpenClose(event) {
 		if (event.defaultPrevented) return;
 		event.preventDefault();
 		// don't toggle if the user was resizing
 		if (!uiTransient.isResizing) {
-			CanvasPanelTrayActions.toggleOpenClose();
+			CanvasPanelTrayActions.toggleOpenClose(event);
 		}
 		event.target.removeEventListener('mousemove', onMouseMove, true);
 	}
@@ -37,10 +41,29 @@
 	const isOpen = style.height > 25;
 	return (
 		<div className={classNames} data-resizable="top" data-resizable-handle-offset="4" style={style}>
-			<h1 data-open-close-icon={isOpen ? 'open' : 'closed'} onMouseDownCapture={onMouseDown} onClick={onClickToggleOpenClose}>Forwarding Graphs</h1>
+            <div  className="CanvasPanelTray-buttons" onClick={onClickToggleOpenClose}>
+                <button
+                    style={{flex: '1 1 auto'}}
+                    className={ClassNames({'-selected': props.displayedPanel === PANEL.FORWARD})}
+                    onMouseDownCapture={onMouseDown}
+                    data-event={PANEL.FORWARD}>
+                    Forwarding Graphs
+                </button>
+
+                <button
+                    style={{flex: '1 1 auto', borderLeft: '1px solid white'}}
+                    className={ClassNames({'-selected': props.displayedPanel === PANEL.PARAMETER})}
+                    onMouseDownCapture={onMouseDown}
+                    data-event={PANEL.PARAMETER}>
+                        Config Parameter Map
+                </button>
+                <div  data-open-close-icon={isOpen ? 'open' : 'closed'}  style={{flex: '0 1', width: '40px', height: '25px', cursor: 'pointer'}} data-event='arrow'>
+
+                </div>
+            </div>
 			<div className="tray-body">
 				{props.children}
 			</div>
 		</div>
 	);
-}
\ No newline at end of file
+}
diff --git a/skyquake/plugins/composer/src/src/components/ComposerApp.js b/skyquake/plugins/composer/src/src/components/ComposerApp.js
index b5cfa75..e2cf284 100644
--- a/skyquake/plugins/composer/src/src/components/ComposerApp.js
+++ b/skyquake/plugins/composer/src/src/components/ComposerApp.js
@@ -212,6 +212,7 @@
 										 filesState={self.state.filesState}
 										 item={self.state.item}
 										 type={self.state.filterCatalogByTypeValue}
+										 displayedPanel={self.state.displayedPanel}
 										  />
 						 	{
  						 		(self.state.panelTabShown == 'descriptor') ?
diff --git a/skyquake/plugins/composer/src/src/components/ConfigPrimitiveParameters/ConfigPrimitiveParameters.js b/skyquake/plugins/composer/src/src/components/ConfigPrimitiveParameters/ConfigPrimitiveParameters.js
new file mode 100644
index 0000000..3621061
--- /dev/null
+++ b/skyquake/plugins/composer/src/src/components/ConfigPrimitiveParameters/ConfigPrimitiveParameters.js
@@ -0,0 +1,236 @@
+
+/*
+ *
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+
+'use strict';
+
+import d3 from 'd3'
+import React from 'react'
+import Range from '../Range'
+import Button from '../Button'
+import ClassNames from 'classnames'
+import changeCase from 'change-case'
+import LayoutRow from '../LayoutRow'
+import SelectionManager from '../../libraries/SelectionManager'
+import PureRenderMixin from 'react-addons-pure-render-mixin'
+import CatalogItemsActions from '../../actions/CatalogItemsActions'
+import CanvasEditorActions from '../../actions/CanvasEditorActions'
+import DescriptorModelFactory from '../../libraries/model/DescriptorModelFactory'
+import ComposerAppActions from '../../actions/ComposerAppActions'
+import DescriptorModelMetaFactory from '../../libraries/model/DescriptorModelMetaFactory'
+import ComposerAppStore from '../../stores/ComposerAppStore'
+import DeletionManager from '../../libraries/DeletionManager'
+import ContentEditableDiv from '../ContentEditableDiv'
+import TooltipManager from '../../libraries/TooltipManager'
+import HighlightRecordServicePaths from '../../libraries/graph/HighlightRecordServicePaths'
+
+import '../../styles/EditForwardingGraphPaths.scss'
+
+import imgNSD from '../../images/default-catalog-icon.svg'
+import imgFG from '../../../../node_modules/open-iconic/svg/infinity.svg'
+import imgRemove from '../../../../node_modules/open-iconic/svg/trash.svg'
+import imgAdd from '../../../../node_modules/open-iconic/svg/plus.svg'
+import imgConnection from '../../../../node_modules/open-iconic/svg/random.svg'
+import imgClassifier from '../../../../node_modules/open-iconic/svg/spreadsheet.svg'
+import imgReorder from '../../../../node_modules/open-iconic/svg/menu.svg'
+import EditDescriptorModelProperties from '../EditDescriptorModelProperties'
+function configParameterMapMap(ap, i) {
+
+    const context = this;
+    context.vnfapMap = ap;
+    return (
+        <div key={i}>
+        <div>{ap.id}</div>
+        <div>{ap.capability['member-vnf-index']}</div>
+        <div>{ap.capability['capability-ref']}</div>
+
+        </div>
+    )
+    /*
+        // const colors = fg.colors;
+        // const stylePrimary = {borderColor: colors.primary};
+        // const styleSecondary = {borderColor: colors.secondary};
+
+        // context.stylePrimary = stylePrimary;
+        // context.styleSecondary = styleSecondary;
+
+        // const rspMap = fg.rsp.reduce((map, rsp) => {
+        //     map[rsp.id] = rsp;
+        //     rsp.classifier = [];
+        //     return map;
+        // }, {});
+
+        // fg.classifier.forEach(classifier => {
+        //     const rsp = rspMap[classifier.model['rsp-id-ref']];
+        //     if (rsp) {
+        //         rsp.classifier.push(classifier);
+        //     }
+        // });
+
+        // function onClickRemoveForwardingGraph(fg, event) {
+        //     event.preventDefault();
+        //     const root = fg.getRoot();
+        //     fg.remove();
+        //     CatalogItemsActions.catalogItemDescriptorChanged(root);
+        // }
+
+        // function onClickAddClassifier(context, fg, event) {
+        //     event.preventDefault();
+        //     fg.createClassifier();
+        //     CatalogItemsActions.catalogItemDescriptorChanged(fg.getRoot());
+        // }
+
+        // function onClickToggleShowAllFGPaths(fg, event) {
+        //     //event.preventDefault();
+        //     event.stopPropagation();
+        //     fg.uiState.showPaths = event.target.checked;
+        //     fg.rsp.forEach(rsp => rsp.uiState.showPath = event.target.checked);
+        //     CatalogItemsActions.catalogItemMetaDataChanged(fg.getRoot().model);
+        // }
+
+        // if (!fg.uiState.hasOwnProperty('showPaths')) {
+        //     fg.uiState.showPaths = true;
+        //     fg.rsp.forEach(d => d.uiState.showPath = true);
+        // }
+
+        // const toggleSelectAllPaths = (
+        //     <input type="checkbox" name={'show-path' + fg.uid} checked={fg.uiState.showPaths} onChange={() => {}} onClick={onClickToggleShowAllFGPaths.bind(null, fg)} />
+        // );
+
+        // const srpFactory = DescriptorModelFactory.newRecordServicePathFactory({}, fg);
+        // srpFactory.uid = fg.uid + i;
+
+        // const hasServiceFunctionVNFDs = context.containers.filter(d => DescriptorModelFactory.isConstituentVnfdWithServiceChain(d, 'SF')).length > 0;
+
+        // return (
+        //     <div key={i} className={fg.className} data-uid={fg.uid} data-offset-width="true" onClick={onClickSelectAndShowInDetailsPanel.bind(null, fg)} onCut={onCutDelegateToRemove.bind(null, fg)}>
+        //         <div key="outline-indicator" data-outline-indicator="true"></div>
+        //         <div className="header-actions">
+        //             <Button className="remove-forwarding-graph" title="Remove" onClick={onClickRemoveForwardingGraph.bind(null, fg)} src={imgRemove}/>
+        //         </div>
+        //         <LayoutRow primaryActionColumn={toggleSelectAllPaths} secondaryActionColumn={<img className="fg-icon" src={imgFG} width="20px" />}>
+        //             <small>{fg.title}</small>
+        //         </LayoutRow>
+        //         <div>
+        //             <h4>Rendered Service Paths</h4>
+        //             {hasServiceFunctionVNFDs ? fg.recordServicePaths.concat(srpFactory).map(mapRecordServicePath.bind(null, context)) : <small className="no-service-function-chain-msg hint">A VNFD with the chain SF is required to build Rendered Service Paths.</small>}
+        //         </div>
+        //         <div>
+        //             <h4>Classifiers</h4>
+        //             {fg.classifier.map(mapClassifier.bind(null, context))}
+        //             <div className="footer-actions">
+        //                 <div className="row-action-column">
+        //                     <Button className="create-new-classifier" src={imgAdd} width="20px" onClick={onClickAddClassifier.bind(null, context, fg)} label="Add Classifier" />
+        //                 </div>
+        //             </div>
+        //         </div>
+        //     </div>
+        // );
+    */
+
+}
+
+function mapNSD(nsd, i) {
+
+    const context = this;
+    context.nsd = nsd;
+
+    function onClickAddConfigParameterMap(nsd, event) {
+        event.preventDefault();
+        nsd.createConfigParameterMap();
+        CatalogItemsActions.catalogItemDescriptorChanged(nsd.getRoot());
+    }
+
+    const forwardingGraphs = nsd.configParameterMap.map(configParameterMap.bind(context));
+    if (forwardingGraphs.length === 0) {
+        forwardingGraphs.push(
+            <div key="1" className="welcome-message">
+                No Forwarding Graphs to model.
+            </div>
+        );
+    }
+
+    return (
+        <div key={i} className={nsd.className}>
+            {forwardingGraphs}
+            <div className="footer-actions">
+                <div className="row-action-column">
+                    <Button className="create-new-forwarding-graph" src={imgAdd} width="20px" onClick={onClickAddConfigParameterMap.bind(null, nsd)} label="Add new Access Point" />
+                </div>
+            </div>
+        </div>
+    );
+
+}
+
+const ConfigPrimitiveParameters = React.createClass({
+    mixins: [PureRenderMixin],
+    getInitialState: function () {
+        return ComposerAppStore.getState();
+    },
+    getDefaultProps: function () {
+        return {
+            containers: []
+        };
+    },
+    componentWillMount: function () {
+    },
+    componentDidMount: function () {
+    },
+    componentDidUpdate: function () {
+    },
+    componentWillUnmount: function () {
+    },
+    render() {
+        const self = this;
+        const containers = this.props.containers;
+        const context = {
+            component: this,
+            containers: containers
+        };
+
+        const networkService = containers.filter(d => d.type === 'nsd');
+        if (networkService.length === 0) {
+            return <p className="welcome-message">No <img src={imgNSD} width="20px" /> NSD open in the canvas. Try opening an NSD.</p>;
+        }
+        return (
+                <div className="ConfigParameterMap">
+                    {
+                        containers.map(function(c, i) {
+                            if(c.className == 'ConfigParameterMap') {
+                                return <EditDescriptorModelProperties key={i} container={c} width={self.props.width} />
+                            }
+                        })
+                    }
+                </div>
+        )
+        // return (
+        //     <div className=" -with-transitions" data-offset-parent="true">
+        //         <div key="outline-indicator" data-outline-indicator="true"></div>
+        //         {containers.filter(d => d.type === 'nsd').map(mapNSD.bind(context))}
+        //     </div>
+        // );
+
+    }
+});
+
+
+
+export default ConfigPrimitiveParameters;
+//<EditDescriptorModelProperties container={DescriptorModelMetaFactory.createModelInstanceForType('nsd.vnffgd.rsp')} width={this.props.width} />
diff --git a/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js b/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js
index 4ee4345..2a5da21 100644
--- a/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js
+++ b/skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js
@@ -46,6 +46,8 @@
 
 import '../styles/EditDescriptorModelProperties.scss'
 
+
+
 function getDescriptorMetaBasicForType(type) {
 	const basicPropertiesFilter = d => _.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
 	return DescriptorModelMetaFactory.getModelMetaForType(type, basicPropertiesFilter) || {properties: []};
@@ -70,16 +72,7 @@
 		return model.id;
 	}
 }
-
-export default function EditDescriptorModelProperties(props) {
-
-	const container = props.container;
-
-	if (!(DescriptorModelFactory.isContainer(container))) {
-		return
-	}
-
-	function startEditing() {
+function startEditing() {
 		DeletionManager.removeEventListeners();
 	}
 
@@ -262,7 +255,7 @@
 		});
 	}
 
-	function buildChoice(container, property, path, value, key) {
+    function buildChoice(container, property, path, value, key, props={}) {
 
 		function onFormFieldValueChanged(event) {
 			if (DescriptorModelFactory.isContainer(this)) {
@@ -516,15 +509,15 @@
 			if (isMissingDescriptorMeta) {
 				field = <span key={key.concat('warning').join(':')} 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, key.join(':'), props);
 			} else if (isSimpleListView) {
-				field = buildSimpleListItem(container, property, valuePath, value, key, index);
+                field = buildSimpleListItem(container, property, valuePath, value, key, index, props);
 			} else if (isLeafList) {
-				field = buildLeafListItem(container, property, valuePath, value, key, index);
+                field = buildLeafListItem(container, property, valuePath, value, key, index, props);
 			} else if (hasProperties) {
-				field = buildElement(container, property, valuePath, value, key.join(':'))
+                field = buildElement(container, property, valuePath, value, key.join(':'), props)
 			} else {
-				field = buildField(container, property, valuePath, value, key.join(':'));
+                field = buildField(container, property, valuePath, value, key.join(':'), props);
 			}
 
 			function onClickLeaf(property, path, value, event) {
@@ -594,8 +587,17 @@
 		);
 
 	}
+export default function EditDescriptorModelProperties(props, type) {
 
-	const containerType = container.uiState['qualified-type'] || container.uiState.type;
+    const container = props.container;
+
+    if (!(DescriptorModelFactory.isContainer(container))) {
+        return
+    }
+
+
+
+    const containerType = (_.isEmpty(type) ? false : type)|| container.uiState['qualified-type'] || container.uiState.type;
 	const basicProperties = getDescriptorMetaBasicForType(containerType).properties;
 
 	function buildBasicGroup() {
@@ -633,7 +635,7 @@
 					{properties.map(property => {
 						const path = [property.name];
 						const value = container.model[property.name];
-						return build(container, property, path, value, {toggle: true, width: props.width});
+                        return build(container, property, path, value, _.assign({toggle: true, width: props.width}, props));
 					})}
 				</div>
 				<div className="toggle-bottom-spacer" style={{visibility: 'hidden', 'height': '50%', position: 'absolute'}}>We need this so when the user closes the panel it won't shift away and scare the bj out of them!</div>
@@ -665,3 +667,6 @@
 	);
 
 }
+export {build}
+// export buildElement;
+// export buildChoice;
diff --git a/skyquake/plugins/composer/src/src/components/EditDescriptorModelPropertiesBkUp.js b/skyquake/plugins/composer/src/src/components/EditDescriptorModelPropertiesBkUp.js
new file mode 100644
index 0000000..c8e664a
--- /dev/null
+++ b/skyquake/plugins/composer/src/src/components/EditDescriptorModelPropertiesBkUp.js
@@ -0,0 +1,671 @@
+/*
+ *
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+/**
+ * Created by onvelocity on 1/18/16.
+ *
+ * This class generates the form fields used to edit the CONFD JSON model.
+ */
+'use strict';
+
+import _ from 'lodash'
+import utils from '../libraries/utils'
+import React from 'react'
+import ClassNames from 'classnames'
+import changeCase from 'change-case'
+import toggle from '../libraries/ToggleElementHandler'
+import Button from './Button'
+import Property from '../libraries/model/DescriptorModelMetaProperty'
+import ComposerAppActions from '../actions/ComposerAppActions'
+import CatalogItemsActions from '../actions/CatalogItemsActions'
+import DESCRIPTOR_MODEL_FIELDS from '../libraries/model/DescriptorModelFields'
+import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
+import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
+import SelectionManager from '../libraries/SelectionManager'
+import DeletionManager from '../libraries/DeletionManager'
+import DescriptorModelIconFactory from '../libraries/model/IconFactory'
+import getEventPath from '../libraries/getEventPath'
+import CatalogDataStore from '../stores/CatalogDataStore'
+
+import imgAdd from '../../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg'
+
+import '../styles/EditDescriptorModelProperties.scss'
+
+
+
+function getDescriptorMetaBasicForType(type) {
+    const basicPropertiesFilter = d => _.contains(DESCRIPTOR_MODEL_FIELDS[type], d.name);
+    return DescriptorModelMetaFactory.getModelMetaForType(type, basicPropertiesFilter) || {properties: []};
+}
+
+function getDescriptorMetaAdvancedForType(type) {
+    const advPropertiesFilter = d => !_.contains(DESCRIPTOR_MODEL_FIELDS[type], d.name);
+    return DescriptorModelMetaFactory.getModelMetaForType(type, advPropertiesFilter) || {properties: []};
+}
+
+function getTitle(model = {}) {
+    if (typeof model['short-name'] === 'string' && model['short-name']) {
+        return model['short-name'];
+    }
+    if (typeof model.name === 'string' && model.name) {
+        return model.name;
+    }
+    if (model.uiState && typeof model.uiState.displayName === 'string' && model.uiState.displayName) {
+        return model.uiState.displayName
+    }
+    if (typeof model.id === 'string') {
+        return model.id;
+    }
+}
+
+export default function EditDescriptorModelProperties(props) {
+
+    const container = props.container;
+
+    if (!(DescriptorModelFactory.isContainer(container))) {
+        return
+    }
+
+    function startEditing() {
+        DeletionManager.removeEventListeners();
+    }
+
+    function endEditing() {
+        DeletionManager.addEventListeners();
+    }
+
+    function onClickSelectItem(property, path, value, event) {
+        event.preventDefault();
+        const root = this.getRoot();
+        if (SelectionManager.select(value)) {
+            CatalogItemsActions.catalogItemMetaDataChanged(root.model);
+        }
+    }
+
+    function onFocusPropertyFormInputElement(property, path, value, event) {
+
+        event.preventDefault();
+        startEditing();
+
+        function removeIsFocusedClass(event) {
+            event.target.removeEventListener('blur', removeIsFocusedClass);
+            Array.from(document.querySelectorAll('.-is-focused')).forEach(d => d.classList.remove('-is-focused'));
+        }
+
+        removeIsFocusedClass(event);
+
+        const propertyWrapper = getEventPath(event).reduce((parent, element) => {
+            if (parent) {
+                return parent;
+            }
+            if (!element.classList) {
+                return false;
+            }
+            if (element.classList.contains('property')) {
+                return element;
+            }
+        }, false);
+
+        if (propertyWrapper) {
+            propertyWrapper.classList.add('-is-focused');
+            event.target.addEventListener('blur', removeIsFocusedClass);
+        }
+
+    }
+
+    function buildAddPropertyAction(container, property, path) {
+        function onClickAddProperty(property, path, event) {
+            event.preventDefault();
+            //SelectionManager.resume();
+            const create = Property.getContainerCreateMethod(property, this);
+            if (create) {
+                const model = null;
+                create(model, path, property);
+            } else {
+                const name = path.join('.');
+                const value = Property.createModelInstance(property);
+                utils.assignPathValue(this.model, name, value);
+            }
+            CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+        }
+        return (
+                <Button className="inline-hint" onClick={onClickAddProperty.bind(container, property, path)} label="Add" src={imgAdd} />
+        );
+    }
+
+    function buildRemovePropertyAction(container, property, path) {
+        function onClickRemoveProperty(property, path, event) {
+            event.preventDefault();
+            const name = path.join('.');
+            const removeMethod = Property.getContainerMethod(property, this, 'remove');
+            if (removeMethod) {
+                removeMethod(utils.resolvePath(this.model, name));
+            } else {
+                utils.removePathValue(this.model, name);
+            }
+            CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+        }
+        return (
+            <Button className="remove-property-action inline-hint" title="Remove" onClick={onClickRemoveProperty.bind(container, property, path)} label="Remove" src={imgRemove}/>
+        );
+    }
+
+    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 isEditable = true;
+        const isGuid = Property.isGuid(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;
+        if (isEnumeration) {
+            const enumeration = Property.getEnumeration(property, value);
+            const options = enumeration.map((d, i) => {
+                // note yangforge generates values for enums but the system does not use them
+                // so we categorically ignore them
+                // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
+                //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
+                return <option key={fieldKey.toString() + ':' + 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>);
+            }
+            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} readOnly={!isEditable}>{options}</select>;
+        }
+
+        if (isLeafRef) {
+            let fullFieldKey = fieldKey;
+            let containerRef = container;
+            while (containerRef.parent) {
+                fullFieldKey = containerRef.parent.key + ':' + fullFieldKey;
+                containerRef = containerRef.parent;
+            }
+            const leafRefPathValues = Property.getLeafRef(property, path, value, fullFieldKey, catalogs, container);
+
+            const options = leafRefPathValues && leafRefPathValues.map((d, i) => {
+                return <option key={fieldKey.toString() + ':' + 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>);
+            }
+            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} readOnly={!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 <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}
+        />;
+
+    }
+
+    function buildElement(container, property, valuePath, value) {
+        return property.properties.map((property, index) => {
+            let childValue;
+            const childPath = valuePath.slice();
+            if (typeof value === 'object') {
+                childValue = value[property.name];
+            }
+            if(property.type != 'choice'){
+                        childPath.push(property.name);
+            }
+            return build(container, property, childPath, childValue);
+
+        });
+    }
+
+    function buildChoice(container, property, path, value, key) {
+
+        function onFormFieldValueChanged(event) {
+            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".
+                    A choice can only ever have one option selected which allows
+                    the system to determine which type is selected by the name of
+                    the element contained within the field.
+                 */
+                /*
+                    const stateExample = {
+                        uiState: {
+                            choice: {
+                                'conf-config': {
+                                    selected: 'rest',
+                                    'case': {
+                                        rest: {},
+                                        netconf: {},
+                                        script: {}
+                                    }
+                                }
+                            }
+                        }
+                    };
+                */
+                const statePath = ['uiState.choice'].concat(name);
+                const stateObject = utils.resolvePath(this.model, statePath.join('.')) || {};
+                const selected = stateObject.selected ? stateObject.selected.split('.')[1] : undefined;
+                // write state back to the model so the new state objects are captured
+                utils.assignPathValue(this.model, statePath.join('.'), stateObject);
+
+                // write the current choice value into the state
+                let choiceObject = utils.resolvePath(this.model, [name, selected].join('.'));
+                let isTopCase = false;
+                if (!choiceObject) {
+                    isTopCase = true;
+                    choiceObject = utils.resolvePath(this.model, [selected].join('.'));
+                }
+                utils.assignPathValue(stateObject, [selected].join('.'), _.cloneDeep(choiceObject));
+
+                if(selected) {
+                    if(this.model.uiState.choice.hasOwnProperty(name)) {
+                        delete this.model[selected];
+                        utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
+                    } else {
+                        // remove the current choice value from the model
+                        utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
+                    }
+                }
+
+                // get any state for the new selected choice
+                const newChoiceObject = utils.resolvePath(stateObject, [value].join('.')) || {};
+
+                // assign new choice value to the model
+                if (isTopCase) {
+                    utils.assignPathValue(this.model, [name, value].join('.'), newChoiceObject);
+                } else {
+                    utils.assignPathValue(this.model, [value].join('.'), newChoiceObject)
+                }
+
+
+                // update the selected name
+                utils.assignPathValue(this.model, statePath.concat('selected').join('.'), value);
+
+                CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+            }
+        }
+
+        const caseByNameMap = {};
+
+        const onChange = onFormFieldValueChanged.bind(container);
+
+        const cases = property.properties.map(d => {
+            if (d.type === 'case') {
+                caseByNameMap[d.name] = d.properties[0];
+                return {
+                    optionName: d.name,
+                    optionTitle: d.description,
+                    //represents case name and case element name
+                    optionValue: [d.name, d.properties[0].name].join('.')
+                };
+            }
+            caseByNameMap[d.name] = d;
+            return {optionName: d.name};
+        });
+
+        const options = [{optionName: '', optionValue: false}].concat(cases).map((d, i) => {
+            return (
+                <option key={i} value={d.optionValue} title={d.optionTitle}>
+                    {d.optionName}
+                    {i ? null : changeCase.title(property.name)}
+                </option>
+            );
+        });
+
+        const selectName = path.join('.');
+        let selectedOptionPath = ['uiState.choice', selectName, '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);
+            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);
+                    }
+                });
+                selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, '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('.'));
+                    }
+                })
+                selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.'));
+            }
+        }
+        //If selectedOptionValue is present, take first item in string which represents the case name.
+        const valueProperty = caseByNameMap[selectedOptionValue ? selectedOptionValue.split('.')[0] : undefined] || {properties: []};
+        const isLeaf = Property.isLeaf(valueProperty);
+        const hasProperties = _.isArray(valueProperty.properties) && valueProperty.properties.length;
+        const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(valueProperty);
+        //Some magic that prevents errors for arising
+        const valueResponse = valueProperty.properties.length ? valueProperty.properties.map((d, i) => {
+            const childPath = path.concat(valueProperty.name, d.name);
+            const childValue = utils.resolvePath(container.model, childPath.join('.'));
+            return (
+                <div key={childPath.concat('info', i).join(':')}>
+                    {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}>
+                    {options}
+                </select>
+                {valueResponse}
+            </div>
+        );
+
+    }
+
+    function buildSimpleListItem(container, property, path, value, key, 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)}>
+                    <img src={req('./' + DescriptorModelIconFactory.getUrlForType(property.name))} width="20px" />
+                    <span>{title}</span>
+                </a>
+                {buildRemovePropertyAction(container, property, path)}
+            </div>
+        );
+    }
+
+    function buildRemoveListItem(container, property, valuePath, fieldKey, index) {
+        const className = ClassNames(property.name + '-remove actions');
+        return (
+            <div key={fieldKey.concat(index).join(':')} className={className}>
+                <h3>
+                    <span className={property.type + '-name name'}>{changeCase.title(property.name)}</span>
+                    <span className="info">{index + 1}</span>
+                    {buildRemovePropertyAction(container, property, valuePath)}
+                </h3>
+            </div>
+        );
+    }
+
+    function buildLeafListItem(container, property, valuePath, value, key, 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>
+
+        );
+    }
+
+    function build(container, property, path, value, props = {}) {
+
+        const fields = [];
+        const isLeaf = Property.isLeaf(property);
+        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};
+
+        if (!property.properties && isObject) {
+            const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
+            property.properties = uiState.properties;
+        }
+
+        const hasProperties = _.isArray(property.properties) && property.properties.length;
+        const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(property);
+
+        // ensure value is not undefined for non-leaf property types
+        if (isObject) {
+            if (typeof value !== 'object') {
+                value = isArray ? [] : {};
+            }
+        }
+        const valueAsArray = _.isArray(value) ? value : isLeafList && typeof value === 'undefined' ? [] : [value];
+
+        const isMetaField = property.name === 'meta';
+        const isCVNFD = property.name === 'constituent-vnfd';
+        const isSimpleListView = Property.isSimpleList(property);
+
+        valueAsArray.forEach((value, index) => {
+
+            let field;
+            const key = fieldKey.slice();
+            const valuePath = path.slice();
+
+            if (isArray) {
+                valuePath.push(index);
+                key.push(index);
+            }
+
+            if (isMetaField) {
+                if (typeof value === 'object') {
+                    value = JSON.stringify(value, undefined, 12);
+                } else if (typeof value !== 'string') {
+                    value = '{}';
+                }
+            }
+
+            if (isMissingDescriptorMeta) {
+                field = <span key={key.concat('warning').join(':')} className="warning">No Descriptor Meta for {property.name}</span>;
+            } else if (property.type === 'choice') {
+                field = buildChoice(container, property, valuePath, value, key.join(':'));
+            } else if (isSimpleListView) {
+                field = buildSimpleListItem(container, property, valuePath, value, key, index);
+            } else if (isLeafList) {
+                field = buildLeafListItem(container, property, valuePath, value, key, index);
+            } else if (hasProperties) {
+                field = buildElement(container, property, valuePath, value, key.join(':'))
+            } else {
+                field = buildField(container, property, valuePath, value, key.join(':'));
+            }
+
+            function onClickLeaf(property, path, value, event) {
+                if (event.isDefaultPrevented()) {
+                    return;
+                }
+                event.preventDefault();
+                event.stopPropagation();
+                this.getRoot().uiState.focusedPropertyPath = path.join('.');
+                console.log('property selected', path.join('.'));
+                ComposerAppActions.propertySelected([path.join('.')]);
+            }
+
+            const clickHandler = isLeaf ? onClickLeaf : () => {};
+            const isContainerList = isArray && !(isSimpleListView || isLeafList);
+
+            fields.push(
+                <div key={fieldKey.concat(['property-content', index]).join(':')}
+                     className={ClassNames('property-content', {'simple-list': isSimpleListView})}
+                     onClick={clickHandler.bind(container, property, valuePath, value)}>
+                    {isContainerList ? buildRemoveListItem(container, property, valuePath, fieldKey, index) : null}
+                    {field}
+                </div>
+            );
+
+        });
+
+        classNames['-is-leaf'] = isLeaf;
+        classNames['-is-array'] = isArray;
+        classNames['cols-' + columnCount] = isColumnar;
+
+        if (property.type === 'choice') {
+            value = utils.resolvePath(container.model, ['uiState.choice'].concat(path, 'selected').join('.'));
+            if(!value) {
+                property.properties.map(function(p) {
+                    let pname = p.properties[0].name;
+                    if(container.model.hasOwnProperty(pname)) {
+                        value = container.model[pname];
+                    }
+                })
+            }
+        }
+
+        let displayValue = typeof value === 'object' ? '' : value;
+        const displayValueInfo = isArray ? valueAsArray.filter(d => typeof d !== 'undefined').length + ' items' : '';
+
+        const onFocus = isLeaf ? event => event.target.classList.add('-is-focused') : false;
+
+        return (
+            <div key={fieldKey.join(':')} className={ClassNames(property.type + '-property property', classNames)} onFocus={onFocus}>
+                <h3 className="property-label">
+                    <label htmlFor={fieldKey.join(':')}>
+                        <span className={property.type + '-name name'}>{title}</span>
+                        <small>
+                            <span className={property.type + '-info info'}>{displayValueInfo}</span>
+                            <span className={property.type + '-value value'}>{displayValue}</span>
+                        </small>
+                        {isArray ? buildAddPropertyAction(container, property, path.concat(valueAsArray.length)) : null}
+                    </label>
+                </h3>
+                <span className={property.type + '-description description'}>{property.description}</span>
+                <val className="property-value">
+                    {isCVNFD ? <span className={property.type + '-tip tip'}>Drag a VNFD from the Catalog to add more.</span> : null}
+                    {fields}
+                </val>
+            </div>
+        );
+
+    }
+
+    const containerType = container.uiState['qualified-type'] || container.uiState.type;
+    const basicProperties = getDescriptorMetaBasicForType(containerType).properties;
+
+    function buildBasicGroup() {
+        if (basicProperties.length === 0) {
+            return null;
+        }
+        return (
+            <div className="basic-properties-group">
+                <h2>Basic</h2>
+                <div>
+                    {basicProperties.map(property => {
+                        const path = [property.name];
+                        const value = container.model[property.name];
+                        return build(container, property, path, value);
+                    })}
+                </div>
+            </div>
+        );
+    }
+
+    function buildAdvancedGroup() {
+        const properties = getDescriptorMetaAdvancedForType(containerType).properties;
+        if (properties.length === 0) {
+            return null;
+        }
+        const hasBasicFields = basicProperties.length > 0;
+        const closeGroup = basicProperties.length > 0;
+        return (
+            <div className="advanced-properties-group">
+                <h1 data-toggle={closeGroup ? 'true' : 'false'} className={ClassNames({'-is-toggled': closeGroup})} onClick={toggle} style={{display: hasBasicFields ? 'block' : 'none'}}>
+                    <a className="toggle-show-more" href="#show-more-properties">more&hellip;</a>
+                    <a className="toggle-show-less" href="#show-more-properties">less&hellip;</a>
+                </h1>
+                <div className="toggleable">
+                    {properties.map(property => {
+                        const path = [property.name];
+                        const value = container.model[property.name];
+                        return build(container, property, path, value, {toggle: true, width: props.width});
+                    })}
+                </div>
+                <div className="toggle-bottom-spacer" style={{visibility: 'hidden', 'height': '50%', position: 'absolute'}}>We need this so when the user closes the panel it won't shift away and scare the bj out of them!</div>
+            </div>
+        );
+    }
+
+    function buildMoreLess(d, i) {
+        return (
+            <span key={'bread-crumb-part-' + i}>
+                <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
+                <i> / </i>
+            </span>
+        );
+    }
+
+    const path = [];
+    if (container.parent) {
+        path.push(container.parent);
+    }
+    path.push(container);
+
+    return (
+        <div className="EditDescriptorModelProperties -is-tree-view">
+            <h1>{path.map(buildMoreLess)}</h1>
+            {buildBasicGroup()}
+            {buildAdvancedGroup()}
+        </div>
+    );
+
+}
+// export buildElement;
+// export buildChoice;
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModel.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModel.js
index 1a2ba4f..b0acc08 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModel.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModel.js
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFactory.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFactory.js
index ac59872..d5b6a28 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFactory.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFactory.js
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -51,7 +51,7 @@
 import InternalConnectionPointRef from './descriptors/InternalConnectionPointRef'
 import VirtualNetworkFunctionConnectionPoint from './descriptors/VirtualNetworkFunctionConnectionPoint'
 import VirtualDeploymentUnitInternalConnectionPoint from './descriptors/VirtualDeploymentUnitInternalConnectionPoint'
-
+import VirtualNetworkFunctionAccessPointMap from './descriptors/VirtualNetworkFunctionAccessPointMap'
 function findChildDescriptorModelAndUpdateModel(model, parent) {
 	if (parent instanceof DescriptorModel) {
 		const child = parent.findChildByUid(model);
@@ -120,6 +120,9 @@
 			fg.rsp.forEach(rsp => mapRSP(rsp, containerList));
 			fg.classifier.forEach(classifier => mapClassifier(classifier, containerList));
 		}
+        function mapConfigParameterMap(ap, containerList) {
+            containerList.push(ap);
+        }
 
 		function mapVDU(vdu, containerList) {
 			containerList.push(vdu);
@@ -143,6 +146,7 @@
 			nsd.constituentVnfd.forEach(cvnfd => mapCVNFD(cvnfd, containerList));
 			nsd.vld.forEach(vld => mapVLD(vld, containerList));
 			nsd.vnffgd.forEach(fg => mapFG(fg, containerList));
+            nsd.configParameterMap.forEach(ap => mapConfigParameterMap(ap, containerList));
 		}
 
 		function mapVNFD(vnfd, containerList) {
@@ -226,6 +230,9 @@
 		return findChildDescriptorModelAndUpdateModel(model, parent) || new VnfdConnectionPointRef(model, parent);
 	}
 
+    static newVirtualNetworkFunctionAccessPointMap(model, parent) {
+        return findChildDescriptorModelAndUpdateModel(model, parent) || new VirtualNetworkFunctionAccessPointMap(model, parent);
+    }
 	static newForwardingGraph(model, parent) {
 		return findChildDescriptorModelAndUpdateModel(model, parent) || new ForwardingGraph(model, parent);
 	}
@@ -322,6 +329,9 @@
 		return obj instanceof VirtualNetworkFunction;
 	}
 
+    static isVirtualNetworkFunctionAccessPointMap(obj) {
+        return obj instanceof VirtualNetworkFunctionAccessPointMap;
+    }
 	static isForwardingGraph(obj) {
 		return obj instanceof ForwardingGraph;
 	}
@@ -342,6 +352,9 @@
 		return NetworkService;
 	}
 
+    static get VirtualNetworkFunctionAccessPointMap () {
+        return VirtualNetworkFunctionAccessPointMap;
+    }
 	static get ForwardingGraph () {
 		return ForwardingGraph;
 	}
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFields.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFields.js
index bc55760..bdbf403 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFields.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFields.js
@@ -28,6 +28,7 @@
 	vld: common.concat([]),
 	vnfd: common.concat(['vdu', 'internal-vld']),
 	'vnfd.vdu': common.concat(['image', 'external-interface', 'vm-flavor', 'cloud-init', 'filename']),
+    'nsd.config-parameter-map': common.concat([]),
 	// white-list valid fields to send in the meta field
 	meta: ['containerPositionMap']
 };
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaFactory.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaFactory.js
index d3ef200..b8624ec 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaFactory.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaFactory.js
@@ -14,6 +14,7 @@
 
 const exportInnerTypesMap = {
 	'constituent-vnfd': 'nsd.constituent-vnfd',
+    'config-parameter-map': 'nsd.config-parameter-map',
 	'vdu': 'vnfd.vdu'
 };
 
diff --git a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelSerializer.js b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelSerializer.js
index 8ce90cf..449f0a3 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelSerializer.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/DescriptorModelSerializer.js
@@ -29,6 +29,7 @@
 let vldFields = null;
 let vnfdFields = null;
 let cvnfdFields = null;
+let configParameterMapFields = null;
 
 
 
@@ -197,6 +198,13 @@
 			const confd = _.omit(copy, ['uiState']);
 			return cleanEmptyTopKeys(confd);
 		}
+    },
+    'config-parameter-map': {
+        serialize(configParameterMap) {
+            //vnfapMapFields
+            if(!configParameterMapFields) configParameterMapFields = DescriptorModelMetaFactory.getModelFieldNamesForType('nsd.config-parameter-map');
+            return _.pick(configParameterMap, configParameterMapFields);
+        }
 	}
 };
 
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..494bf38 100644
--- a/skyquake/plugins/composer/src/src/libraries/model/descriptors/NetworkService.js
+++ b/skyquake/plugins/composer/src/src/libraries/model/descriptors/NetworkService.js
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,6 +25,7 @@
 import ColorGroups from '../../ColorGroups'
 import DescriptorModel from '../DescriptorModel'
 import ForwardingGraph from './ForwardingGraph'
+import VirtualNetworkFunctionAccessPointMap from './VirtualNetworkFunctionAccessPointMap'
 import VirtualLink from './VirtualLink'
 import ConstituentVnfd from './ConstituentVnfd'
 import PhysicalNetworkFunction from './PhysicalNetworkFunction'
@@ -128,6 +129,45 @@
 	}
 
 
+// <<<<<<< Updated upstream
+//     get configParameterMap() {
+//         if (!this.model['config-parameter-map']) {
+//             this.model['config-parameter-map'] = [];
+//         }
+//         return this.model['config-parameter-map'].map(d => DescriptorModelFactory.newVirtualNetworkFunctionAccessPointMap(d, this)).map((fg, i) => {
+//             return fg;
+//         });
+//     }
+
+//     set configParameterMap(obj) {
+//         const onVirtualNetworkFunctionAccessPointMap = (fg) => {
+
+//         };
+//         this.updateModelList('config-parameter-map', obj, VirtualNetworkFunctionAccessPointMap, onVirtualNetworkFunctionAccessPointMap);
+//     }
+
+//     createConfigParameterMap(model) {
+//         model = model || DescriptorModelMetaFactory.createModelInstanceForType('nsd.config-parameter-map');
+//         return this.configParameterMap = DescriptorModelFactory.newVirtualNetworkFunctionAccessPointMap(model, this);
+//     }
+// =======
+	get configParameterMap() {
+		if (!this.model['config-parameter-map']) {
+			this.model['config-parameter-map'] = [];
+		}
+		return this.model['config-parameter-map'].map(d => DescriptorModelFactory.newVirtualNetworkFunctionAccessPointMap(d, this))
+	}
+
+	set configParameterMap(obj) {
+		this.updateModelList('config-parameter-map', obj, VirtualNetworkFunctionAccessPointMap);
+	}
+
+	createConfigParameterMap() {
+		const model = DescriptorModelMetaFactory.createModelInstanceForType('nsd.config-parameter-map');
+		return this.configParameterMap = DescriptorModelFactory.newVirtualNetworkFunctionAccessPointMap(model, this);
+	}
+// >>>>>>> Stashed changes
+
 	get vnffgd() {
 		if (!this.model.vnffgd) {
 			this.model.vnffgd = [];
@@ -160,6 +200,7 @@
 	}
 
 
+
 	// NOTE temporarily disable NSD connection points
 	// https://trello.com/c/crVgg2A1/88-do-not-render-nsd-connection-in-the-composer
 	//get connectionPoint() {
diff --git a/skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunctionAccessPoint.js b/skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunctionAccessPoint.js
new file mode 100644
index 0000000..defa317
--- /dev/null
+++ b/skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunctionAccessPoint.js
@@ -0,0 +1,57 @@
+/*
+ *
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+/**
+ * Created by onvelocity on 11/23/15.
+ */
+
+'use strict';
+
+import DescriptorModel from '../DescriptorModel'
+import DescriptorModelFactory from '../DescriptorModelFactory'
+
+export default class VnfapMap extends DescriptorModel {
+
+    static get type() {
+        return 'vnfap-map';
+    }
+
+    static get className() {
+        return 'VnfapMap';
+    }
+
+    constructor(model, parent) {
+        super(model, parent);
+        this.type = 'vnfap-map';
+        this.uiState['qualified-type'] = 'nsd.vnfap-map';
+        this.className = 'VnfapMap';
+        // this.addProp('vnfdRef', DescriptorModelFactory.newVirtualNetworkFunctionReadOnlyWrapper({}, this));
+    }
+
+    get id() {
+        return this.model.id;
+    }
+    get capability() {
+        return []
+    }
+
+    get dependency() {
+
+    }
+
+
+}
diff --git a/skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunctionAccessPointMap.js b/skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunctionAccessPointMap.js
new file mode 100644
index 0000000..16b57dc
--- /dev/null
+++ b/skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunctionAccessPointMap.js
@@ -0,0 +1,99 @@
+/*
+ *
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+/**
+ * Created by onvelocity on 11/23/15.
+ */
+
+'use strict';
+
+import DescriptorModel from '../DescriptorModel'
+import DescriptorModelFactory from '../DescriptorModelFactory'
+
+export default class ConfigParameterMap extends DescriptorModel {
+
+// <<<<<<< Updated upstream
+//     static get type() {
+//         return 'config-parameter-map';
+//     }
+
+//     static get className() {
+//         return 'ConfigParameterMap';
+//     }
+
+//     constructor(model, parent) {
+//         super(model, parent);
+//         this.type = 'config-parameter-map';
+//         this.uiState['qualified-type'] = 'nsd.config-parameter-map';
+//         this.className = 'ConfigParameterMap';
+//         // this.addProp('vnfdRef', DescriptorModelFactory.newVirtualNetworkFunctionReadOnlyWrapper({}, this));
+//     }
+
+//     get id() {
+//         return this.model.id;
+//     }
+//     get capability() {
+//         return {
+//             'member-vnf-index': this.model.capability['member-vnf-index'],
+//             'capability-ref': this.model.capability['capability-ref'],
+//         }
+//     }
+//     get availableCapabilities() {
+
+//     }
+
+//     get dependency() {
+
+//     }
+// =======
+	static get type() {
+		return 'config-parameter-map';
+	}
+
+	static get className() {
+		return 'ConfigParameterMap';
+	}
+	static get qualifiedType() {
+		return 'nsd.' + ConfigParameterMap.type;
+	}
+	constructor(model, parent) {
+		super(model, parent);
+		this.type = 'config-parameter-map';
+		this.uiState['qualified-type'] = 'nsd.config-parameter-map';
+		this.className = 'ConfigParameterMap';
+		// this.addProp('vnfdRef', DescriptorModelFactory.newVirtualNetworkFunctionReadOnlyWrapper({}, this));
+	}
+
+	get id() {
+		return this.model.id;
+	}
+	// get capability() {
+	// 	return {
+	// 		'member-vnf-index': this.model.capability['member-vnf-index'],
+	// 		'capability-ref': this.model.capability['capability-ref'],
+	// 	}
+	// }
+	// get availableCapabilities() {
+
+	// }
+
+	// get dependency() {
+
+	// }
+
+
+}
diff --git a/skyquake/plugins/composer/src/src/libraries/utils.js b/skyquake/plugins/composer/src/src/libraries/utils.js
index a182b1a..bcf5646 100644
--- a/skyquake/plugins/composer/src/src/libraries/utils.js
+++ b/skyquake/plugins/composer/src/src/libraries/utils.js
@@ -271,11 +271,11 @@
 
 		// Check if relative path or not
 		// TODO: Below works but
-		// better to convert the pathCopy to absolute/rooted path 
+		// better to convert the pathCopy to absolute/rooted path
 		// and use the absolute module instead
 		if (this.isRelativePath(leafRefPathCopy)) {
 			let i = pathArray.length;
-			while (pathArray[pathArray.length - i] == '..') {
+			while ((pathArray[pathArray.length - i] == '..') && fieldKeyArray.length > 1) {
 				fieldKeyArray.splice(-1, 1);
 				if (!isNaN(Number(fieldKeyArray[fieldKeyArray.length - 1]))) {
 					// found a number, so an index. strip it
@@ -310,7 +310,7 @@
 						}
 					}
 				}
-			} else {
+			}  else {
 				// not supported - too many levels deep ... maybe some day
 				console.log('The relative path is from a node too many levels deep from root. This is not supported at the time');
 			}
diff --git a/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js b/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js
index 22d6359..ea3f474 100644
--- a/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js
+++ b/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js b/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js
index 854864b..30352a5 100644
--- a/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js
+++ b/skyquake/plugins/composer/src/src/stores/ComposerAppStore.js
@@ -341,13 +341,21 @@
 		this.setState({showJSONViewer: false});
 	}
 
-	toggleCanvasPanelTray() {
+	toggleCanvasPanelTray(event) {
 		const layout = this.layout;
-		if (layout.bottom > 25) {
+		const attrMap = event.target.attributes;
+		let panelEvent = null;
+		for(let k in attrMap) {
+			if(attrMap[k].name == 'data-event') {
+				panelEvent = attrMap[k].nodeValue;
+			}
+		}
+		if ((layout.bottom > 25) && ((panelEvent == this.displayedPanel) || panelEvent == 'arrow')) {
 			this.closeCanvasPanelTray();
 		} else {
 			this.openCanvasPanelTray();
 		}
+		this.setState({displayedPanel: panelEvent})
 	}
 
 	openCanvasPanelTray() {
diff --git a/skyquake/plugins/composer/src/src/styles/CanvasPanel.scss b/skyquake/plugins/composer/src/src/styles/CanvasPanel.scss
index 9fa1149..2f684d2 100644
--- a/skyquake/plugins/composer/src/src/styles/CanvasPanel.scss
+++ b/skyquake/plugins/composer/src/src/styles/CanvasPanel.scss
@@ -28,6 +28,7 @@
 	min-width: 300px;
 	overflow: hidden;
 	z-index: 1;
+
 	.CanvasPanelHeader {
 		h1 {
 			margin: 0;
diff --git a/skyquake/plugins/composer/src/src/styles/CanvasPanelTray.scss b/skyquake/plugins/composer/src/src/styles/CanvasPanelTray.scss
index 7e9e336..55844b6 100644
--- a/skyquake/plugins/composer/src/src/styles/CanvasPanelTray.scss
+++ b/skyquake/plugins/composer/src/src/styles/CanvasPanelTray.scss
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,7 +29,8 @@
 	right: 0;
 	height: 25px;
 	min-width: 300px;
-	background-color: white;
+	/* background-color: white;*/
+	background: #cbd1d1;
 	&.-with-transitions {
 		transition: height 300ms cubic-bezier(0.230, 1.000, 0.320, 1.000);
 	}
@@ -60,4 +61,42 @@
 		}
 	}
 
+
+	&-buttons {
+		display: -ms-flexbox;
+		display: flex;
+		margin-top: 1px;
+	}
+	.tray-body {
+		top:31px;
+	}
+	.ConfigParameterMap {
+
+		background: #cbd1d1;
+
+		.EditDescriptorModelProperties {
+		    margin-left: 8px;
+		}
+
+		.toggleable {
+			display:-ms-flexbox;
+			display:flex;
+			-ms-flex-wrap: wrap;
+			    flex-wrap: wrap;
+		 	& > .leaf-property {
+			    -ms-flex: 1 0 100%;
+		        flex: 1 0 100%;
+		        margin: 8px 0;
+				padding: 0 8px;
+				background: none;
+		        .property-label {
+		        }
+			}
+			& > .container-property {
+				-ms-flex: 1 1;
+				    flex: 1 1;
+			}
+		}
+	}
+
 }