RIFT-15154: Config parameter map 99/899/1
authorLaurence Maultsby <laurence.maultsby@riftio.com>
Tue, 10 Jan 2017 02:25:23 +0000 (21:25 -0500)
committerLaurence Maultsby <laurence.maultsby@riftio.com>
Tue, 10 Jan 2017 02:25:23 +0000 (21:25 -0500)
Signed-off-by: Laurence Maultsby <laurence.maultsby@riftio.com>
19 files changed:
skyquake/plugins/composer/src/src/components/CanvasPanel.js
skyquake/plugins/composer/src/src/components/CanvasPanelTray.js
skyquake/plugins/composer/src/src/components/ComposerApp.js
skyquake/plugins/composer/src/src/components/ConfigPrimitiveParameters/ConfigPrimitiveParameters.js [new file with mode: 0644]
skyquake/plugins/composer/src/src/components/EditDescriptorModelProperties.js
skyquake/plugins/composer/src/src/components/EditDescriptorModelPropertiesBkUp.js [new file with mode: 0644]
skyquake/plugins/composer/src/src/libraries/model/DescriptorModel.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFactory.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorModelFields.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorModelMetaFactory.js
skyquake/plugins/composer/src/src/libraries/model/DescriptorModelSerializer.js
skyquake/plugins/composer/src/src/libraries/model/descriptors/NetworkService.js
skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunctionAccessPoint.js [new file with mode: 0644]
skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunctionAccessPointMap.js [new file with mode: 0644]
skyquake/plugins/composer/src/src/libraries/utils.js
skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js
skyquake/plugins/composer/src/src/stores/ComposerAppStore.js
skyquake/plugins/composer/src/src/styles/CanvasPanel.scss
skyquake/plugins/composer/src/src/styles/CanvasPanelTray.scss

index 96904ea..dda1372 100644 (file)
@@ -34,9 +34,9 @@ import CanvasPanelTray from './CanvasPanelTray'
 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 @@ const CanvasPanel = React.createClass({
                                        </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 @@ const CanvasPanel = React.createClass({
                                                <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>
                );
index 57f29a0..5fde578 100644 (file)
@@ -16,13 +16,17 @@ export default function (props) {
                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 @@ export default function (props) {
        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
+}
index b5cfa75..e2cf284 100644 (file)
@@ -212,6 +212,7 @@ const ComposerApp = React.createClass({
                                                                                 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 (file)
index 0000000..3621061
--- /dev/null
@@ -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} />
index 4ee4345..2a5da21 100644 (file)
@@ -46,6 +46,8 @@ import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg'
 
 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 @@ function getTitle(model = {}) {
                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 @@ export default function EditDescriptorModelProperties(props) {
                });
        }
 
-       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 @@ export default function EditDescriptorModelProperties(props) {
                        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) {
                );
 
        }
+export default function EditDescriptorModelProperties(props, type) {
+
+    const container = props.container;
+
+    if (!(DescriptorModelFactory.isContainer(container))) {
+        return
+    }
+
+
 
-       const containerType = container.uiState['qualified-type'] || container.uiState.type;
+    const containerType = (_.isEmpty(type) ? false : type)|| container.uiState['qualified-type'] || container.uiState.type;
        const basicProperties = getDescriptorMetaBasicForType(containerType).properties;
 
        function buildBasicGroup() {
@@ -633,7 +635,7 @@ export default function EditDescriptorModelProperties(props) {
                                        {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 default function EditDescriptorModelProperties(props) {
        );
 
 }
+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 (file)
index 0000000..c8e664a
--- /dev/null
@@ -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;
index 1a2ba4f..b0acc08 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
index ac59872..d5b6a28 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -51,7 +51,7 @@ import VirtualNetworkFunctionReadOnlyWrapper from './descriptors/VirtualNetworkF
 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 @@ class DescriptorModelFactory {
                        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 @@ class DescriptorModelFactory {
                        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 @@ class DescriptorModelFactory {
                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 @@ class DescriptorModelFactory {
                return obj instanceof VirtualNetworkFunction;
        }
 
+    static isVirtualNetworkFunctionAccessPointMap(obj) {
+        return obj instanceof VirtualNetworkFunctionAccessPointMap;
+    }
        static isForwardingGraph(obj) {
                return obj instanceof ForwardingGraph;
        }
@@ -342,6 +352,9 @@ class DescriptorModelFactory {
                return NetworkService;
        }
 
+    static get VirtualNetworkFunctionAccessPointMap () {
+        return VirtualNetworkFunctionAccessPointMap;
+    }
        static get ForwardingGraph () {
                return ForwardingGraph;
        }
index bc55760..bdbf403 100644 (file)
@@ -28,6 +28,7 @@ export default {
        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']
 };
index d3ef200..b8624ec 100644 (file)
@@ -14,6 +14,7 @@ const assign = Object.assign;
 
 const exportInnerTypesMap = {
        'constituent-vnfd': 'nsd.constituent-vnfd',
+    'config-parameter-map': 'nsd.config-parameter-map',
        'vdu': 'vnfd.vdu'
 };
 
index 8ce90cf..449f0a3 100644 (file)
@@ -29,6 +29,7 @@ let nsdFields = null;
 let vldFields = null;
 let vnfdFields = null;
 let cvnfdFields = null;
+let configParameterMapFields = null;
 
 
 
@@ -197,6 +198,13 @@ const DescriptorModelSerializer = {
                        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);
+        }
        }
 };
 
index 16fb159..494bf38 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,6 +25,7 @@ import _ from 'lodash'
 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 @@ export default class NetworkService extends DescriptorModel {
        }
 
 
+// <<<<<<< 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 @@ export default class NetworkService extends DescriptorModel {
        }
 
 
+
        // 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 (file)
index 0000000..defa317
--- /dev/null
@@ -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 (file)
index 0000000..16b57dc
--- /dev/null
@@ -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() {
+
+       // }
+
+
+}
index a182b1a..bcf5646 100644 (file)
@@ -271,11 +271,11 @@ export default {
 
                // 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 @@ export default {
                                                }
                                        }
                                }
-                       } 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');
                        }
index 22d6359..ea3f474 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
index 854864b..30352a5 100644 (file)
@@ -341,13 +341,21 @@ class ComposerAppStore {
                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() {
index 9fa1149..2f684d2 100644 (file)
@@ -28,6 +28,7 @@
        min-width: 300px;
        overflow: hidden;
        z-index: 1;
+
        .CanvasPanelHeader {
                h1 {
                        margin: 0;
index 7e9e336..55844b6 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,7 +29,8 @@ $tray-head-height: 25px;
        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 @@ $tray-head-height: 25px;
                }
        }
 
+
+       &-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;
+                       }
+               }
+       }
+
 }