aboutsummaryrefslogtreecommitdiffstats
path: root/openecomp-ui
diff options
context:
space:
mode:
authorMurali-P <murali.p@huawei.com>2017-10-16 15:07:16 +0530
committerMurali-P <murali.p@huawei.com>2017-12-04 11:04:54 +0530
commitbaebe989f102c10d5077fb24a6948893284a236d (patch)
tree84566a75879428a950f04a9c369ab1a80afcd8f7 /openecomp-ui
parentdcf55799473fe5d585ff1bc8121ab48485baab2b (diff)
Integrate VNF Repository with SDC
Add Browse VNF packages feature Change-Id: I0c721829efdac8ad6f72c4ac9d25ec96e091f7ca Issue-ID: VNFSDK-82 Signed-off-by: Michael Lando <ml636r@att.com> Signed-off-by: Murali-P <murali.p@huawei.com>
Diffstat (limited to 'openecomp-ui')
-rw-r--r--openecomp-ui/resources/scss/_components.scss1
-rw-r--r--openecomp-ui/resources/scss/components/_vnfBrowse.scss109
-rw-r--r--openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss34
-rw-r--r--openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx14
-rw-r--r--openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx60
-rw-r--r--openecomp-ui/src/nfvo-utils/RestAPIUtil.js4
-rw-r--r--openecomp-ui/src/nfvo-utils/i18n/en.json10
-rw-r--r--openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js7
-rw-r--r--openecomp-ui/src/sdc-app/config/config.json3
-rw-r--r--openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js43
-rw-r--r--openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js5
-rw-r--r--openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js7
-rw-r--r--openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx47
-rw-r--r--openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js37
-rw-r--r--openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js160
-rw-r--r--openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js22
-rw-r--r--openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js32
-rw-r--r--openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx176
18 files changed, 739 insertions, 32 deletions
diff --git a/openecomp-ui/resources/scss/_components.scss b/openecomp-ui/resources/scss/_components.scss
index 7c726aa8dd..3d7784ca8d 100644
--- a/openecomp-ui/resources/scss/_components.scss
+++ b/openecomp-ui/resources/scss/_components.scss
@@ -19,6 +19,7 @@
@import "components/activityLog";
@import "components/selectActionTable";
@import "components/datepicker";
+@import "components/vnfBrowse";
%noselect {
-webkit-touch-callout: none;
diff --git a/openecomp-ui/resources/scss/components/_vnfBrowse.scss b/openecomp-ui/resources/scss/components/_vnfBrowse.scss
new file mode 100644
index 0000000000..7e0085af8a
--- /dev/null
+++ b/openecomp-ui/resources/scss/components/_vnfBrowse.scss
@@ -0,0 +1,109 @@
+$message-info-icon-size: 16px;
+
+.vnf-creation-page {
+ .list-editor-view-header {
+ border-bottom: none;
+ }
+ .vnfBrowse-list-item {
+ display: flex;
+ height: 36px;
+ @extend .body-1;
+ &.header {
+ @extend .body-1-semibold;
+ background-color: $tlv-light-gray;
+ color: $text-black;
+ }
+ &.selectedRow {
+ background-color: $blue;
+ color: $white;
+ .svg-icon-wrapper {
+ &.__positive {
+ fill: $white;
+ color: $white;
+ }
+ }
+ }
+ .svg-icon-wrapper {
+ &.__positive {
+ fill: $dark-gray;
+ color: $dark-gray;
+ }
+ }
+ }
+
+ .activity-action {
+ .svg-icon-wrapper {
+ float: left;
+ }
+ }
+
+ .message-further-info-icon {
+ background-color: $gray;
+ }
+
+ .table-cell {
+ border-right: 1px solid $light-gray;
+ border-bottom: 1px solid $light-gray;
+ &:last-child {
+ border-right: none;
+ }
+ flex-basis: 22%;
+ display: flex;
+ padding: 0 20px;
+ justify-content: center;
+ flex-direction: column;
+
+ &.vnftable-action {
+ flex-basis: 12%;
+ span {
+ margin: auto;
+ }
+ }
+}
+
+ .vnf-table-header {
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ .header-sort-arrow {
+ width: 0;
+ height: 0;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ margin-left: 9px;
+ &.up {
+ border-bottom: 5px solid $black;
+ }
+ &.down {
+ border-top: 5px solid $black;
+ }
+
+ }
+ }
+
+ .vnf-table-cell {
+ display: flex;
+ justify-content: space-between;
+ span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ .vnftable-name {
+ max-width: 22%;
+ }
+
+ .vnf-grid-section {
+ margin: 20px 20px 20px 50px;
+ }
+
+ .vnf-modal {
+ text-align: right;
+ margin-top: 22px;
+ }
+
+ .vnf-submit {
+ margin-right: 15px;
+ }
+
+} \ No newline at end of file
diff --git a/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss b/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss
index 28b54cc78f..b674492047 100644
--- a/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss
+++ b/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss
@@ -187,7 +187,41 @@
.or-text {
margin-top: 10px;
margin-bottom: 10px;
+ color: $light-gray;
}
+ .upload {
+ width: 50%;
+ }
+ .vnfRepo {
+ width: 50%;
+ cursor: pointer;
+ .searchRepo-text {
+ color: $blue;
+ @extend .body-1-semibold;
+ width: 72px;
+ line-height: 24px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+ .svg-icon-wrapper {
+ .svg-icon.__search {
+ width: 34px;
+ height: 34px;
+ margin-top: 10px;
+ }
+ &.__positive {
+ fill: $blue;
+ color: $blue;
+ }
+ }
+ }
+ .verticalLine {
+ height: 90%;
+ border-left: 1px solid $light-gray;
+ }
+ }
+ .showVnf {
+ flex-direction: row;
}
}
}
diff --git a/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx b/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx
index 629b9449a2..5bea858ab7 100644
--- a/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx
+++ b/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx
@@ -13,19 +13,7 @@
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-/**
- * The HTML structure here is aligned with bootstrap HTML structure for form elements.
- * In this way we have proper styling and it is aligned with other form elements on screen.
- *
- * Select and MultiSelect options:
- *
- * label - the label to be shown which paired with the input
- *
- * all other "react-select" props - as documented on
- * http://jedwatson.github.io/react-select/
- * or
- * https://github.com/JedWatson/react-select
- */
+
import React, {Component} from 'react';
import i18n from 'nfvo-utils/i18n/i18n.js';
import Button from 'sdc-ui/lib/react/Button.js';
diff --git a/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx b/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx
new file mode 100644
index 0000000000..0673e6d698
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx
@@ -0,0 +1,60 @@
+/*!
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, {Component} from 'react';
+import DraggableUploadFileBox from 'nfvo-components/fileupload/DraggableUploadFileBox.jsx';
+import Configuration from 'sdc-app/config/Configuration.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
+
+function VNFBrowse({onBrowseVNF, isReadOnlyMode}) {
+ if(!Configuration.get('showBrowseVNF')) {
+ return (
+ <div/>
+ );
+ }
+ else {
+ return (
+ <div className={`${'vnfRepo'}${isReadOnlyMode ? ' disabled' : ''}`} onClick={onBrowseVNF}>
+ <div className={`${'searchRepo-text'}`}>{i18n('Search in Repository')}</div>
+ <SVGIcon name='search' color='positive' iconClassName='searchIcon'/>
+ </div>
+ );
+ }
+}
+
+class VnfRepositorySearchBox extends Component {
+ render() {
+ let {className, onClick, onBrowseVNF, dataTestId, isReadOnlyMode} = this.props;
+ let showVNF = Configuration.get('showBrowseVNF');
+ return (
+ <div
+ className={`${className}${isReadOnlyMode ? ' disabled' : ''}`}>
+ <DraggableUploadFileBox
+ dataTestId={dataTestId}
+ isReadOnlyMode={isReadOnlyMode}
+ className={'upload'}
+ onClick={onClick}/>
+
+ <div className={`${'verticalLine'}${showVNF ? '' : ' hide'}`}></div>
+
+ <VNFBrowse onBrowseVNF={onBrowseVNF} isReadOnlyMode={isReadOnlyMode}/>
+ </div>
+ );
+
+ }
+}
+export default VnfRepositorySearchBox;
diff --git a/openecomp-ui/src/nfvo-utils/RestAPIUtil.js b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js
index c878c9e673..648c159908 100644
--- a/openecomp-ui/src/nfvo-utils/RestAPIUtil.js
+++ b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js
@@ -55,6 +55,10 @@ class RestAPIUtil extends RestfulAPI {
applySecurity(options, data) {
let headers = options.headers || (options.headers = {});
+ if (options.isAnonymous) {
+ return;
+ }
+
let authToken = localStorage.getItem(STORAGE_AUTH_KEY);
if (authToken) {
headers[AUTHORIZATION_HEADER] = authToken;
diff --git a/openecomp-ui/src/nfvo-utils/i18n/en.json b/openecomp-ui/src/nfvo-utils/i18n/en.json
index 8ed638352c..5cb2a536a4 100644
--- a/openecomp-ui/src/nfvo-utils/i18n/en.json
+++ b/openecomp-ui/src/nfvo-utils/i18n/en.json
@@ -326,5 +326,13 @@
"Persistent Storage/Volume Size (GB)": "Persistent Storage/Volume Size (GB)",
"I/O Operations (per second)": "I/O Operations (per second)",
"CPU Oversubscription Ratio": "CPU Oversubscription Ratio",
- "Memory - RAM": "Memory - RAM"
+ "Memory - RAM": "Memory - RAM",
+ "VNF List Title": "VNF List",
+ "VNF import failed title" : "VNF import failed",
+ "VNF import failed msg" : "VNF Repository Server is not responding or not reachable. Please check server address in configuration file.",
+ "VNF Header Name" : "Name",
+ "VNF Header Version" : "Version",
+ "VNF Header Vendor" : "Vendor",
+ "VNF Header Desc" : "Description",
+ "VNF Header Action" : "Action"
}
diff --git a/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js b/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js
index 8c10beb952..3d03d00b09 100644
--- a/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js
+++ b/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js
@@ -23,6 +23,7 @@ import NICCreation from 'sdc-app/onboarding/softwareProduct/components/network/N
import SoftwareProductComponentsNICEditor from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNICEditor.js';
import ComponentCreation from 'sdc-app/onboarding/softwareProduct/components/creation/SoftwareProductComponentCreation.js';
import SoftwareProductDeploymentEditor from 'sdc-app/onboarding/softwareProduct/deployment/editor/SoftwareProductDeploymentEditor.js';
+import VNFImport from 'sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js';
export const modalContentMapper = {
SOFTWARE_PRODUCT_CREATION: 'SOFTWARE_PRODUCT_CREATION',
@@ -33,7 +34,8 @@ export const modalContentMapper = {
NIC_CREATION: 'NIC_CREATION',
COMPONENT_CREATION: 'COMPONENT_CREATION',
SOFTWARE_PRODUCT_COMPONENT_IMAGE_EDITOR : 'SOFTWARE_PRODUCT_COMPONENT_IMAGE_EDITOR',
- DEPLOYMENT_FLAVOR_EDITOR: 'DEPLOYMENT_FLAVOR_EDITOR'
+ DEPLOYMENT_FLAVOR_EDITOR: 'DEPLOYMENT_FLAVOR_EDITOR',
+ VNF_IMPORT: 'VNF_IMPORT'
};
export const modalContentComponents = {
@@ -45,5 +47,6 @@ export const modalContentComponents = {
NIC_CREATION: NICCreation,
COMPONENT_CREATION: ComponentCreation,
SOFTWARE_PRODUCT_COMPONENT_IMAGE_EDITOR : SoftwareProductComponentImageEditor,
- DEPLOYMENT_FLAVOR_EDITOR: SoftwareProductDeploymentEditor
+ DEPLOYMENT_FLAVOR_EDITOR: SoftwareProductDeploymentEditor,
+ VNF_IMPORT: VNFImport
};
diff --git a/openecomp-ui/src/sdc-app/config/config.json b/openecomp-ui/src/sdc-app/config/config.json
index 2725cf1310..361088b3dd 100644
--- a/openecomp-ui/src/sdc-app/config/config.json
+++ b/openecomp-ui/src/sdc-app/config/config.json
@@ -4,5 +4,6 @@
"build": "dev",
"appContextPath" : "/onboarding",
"defaultRestPrefix": "/onboarding-api",
- "defaultRestATTPrefix": "/sdc1/feProxy/rest"
+ "defaultRestATTPrefix": "/sdc1/feProxy/rest",
+ "showBrowseVNF" : true
}
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js
index 41306a1c1a..aa41818b97 100644
--- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js
@@ -48,6 +48,10 @@ function uploadFile(vspId, formData, version) {
}
+function uploadVNFFile(csarId, softwareProductId, version) {
+ return RestAPIUtil.post(`${baseUrl()}${softwareProductId}/versions/${version.id}/vnfrepository/vnfpackage/${csarId}/import`);
+}
+
function putSoftwareProduct(softwareProduct) {
return RestAPIUtil.put(`${baseUrl()}${softwareProduct.id}/versions/${softwareProduct.version.id}`, {
name: softwareProduct.name,
@@ -298,6 +302,45 @@ const SoftwareProductActionHelper = {
});
},
+ uploadVNFFile(dispatch, {csarId, failedNotificationTitle, softwareProductId, version}) {
+ dispatch({
+ type: HeatSetupActions.FILL_HEAT_SETUP_CACHE,
+ payload: {}
+ });
+
+ Promise.resolve()
+ .then(() => uploadVNFFile(csarId, softwareProductId, version))
+ .then(response => {
+ if (response.status === 'Success') {
+ dispatch({
+ type: commonActionTypes.DATA_CHANGED,
+ deltaData: {onboardingOrigin: response.onboardingOrigin},
+ formName: forms.VENDOR_SOFTWARE_PRODUCT_DETAILS
+ });
+ switch(response.onboardingOrigin){
+ case onboardingOriginTypes.ZIP:
+ OnboardingActionHelper.navigateToSoftwareProductAttachmentsSetupTab(dispatch, {softwareProductId, version});
+ break;
+ case onboardingOriginTypes.CSAR:
+ OnboardingActionHelper.navigateToSoftwareProductAttachmentsValidationTab(dispatch, {softwareProductId, version});
+ break;
+ }
+ }
+ else {
+ throw new Error(parseUploadErrorMsg(response.errors));
+ }
+ })
+ .catch(error => {
+ dispatch({
+ type: modalActionTypes.GLOBAL_MODAL_ERROR,
+ data: {
+ title: failedNotificationTitle,
+ msg: error.message
+ }
+ });
+ });
+ },
+
downloadHeatFile(dispatch, {softwareProductId, heatCandidate, isReadOnlyMode, version}){
let p = isReadOnlyMode ? Promise.resolve() : SoftwareProductActionHelper.updateSoftwareProductHeatCandidate(dispatch, {softwareProductId, heatCandidate, version});
p.then(() => {
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js
index d7a6c2ef5c..c9159f192e 100644
--- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js
@@ -47,6 +47,8 @@ import {COMPONENTS_QUESTIONNAIRE, COMPONENTS_COMPUTE_QUESTIONNAIRE} from 'sdc-ap
import {NIC_QUESTIONNAIRE} from 'sdc-app/onboarding/softwareProduct/components/network/SoftwareProductComponentsNetworkConstants.js';
import {IMAGE_QUESTIONNAIRE} from 'sdc-app/onboarding/softwareProduct/components/images/SoftwareProductComponentsImageConstants.js';
+import VNFImportReducer from './vnfMarketPlace/VNFImportReducer.js';
+
export default combineReducers({
softwareProductAttachments: combineReducers({
attachmentsDetails: SoftwareProductAttachmentsReducer,
@@ -98,5 +100,6 @@ export default combineReducers({
}
return state;
},
- softwareProductQuestionnaire: createJSONSchemaReducer(PRODUCT_QUESTIONNAIRE)
+ softwareProductQuestionnaire: createJSONSchemaReducer(PRODUCT_QUESTIONNAIRE),
+ VNFMarketPlaceImport: VNFImportReducer
});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js
index a13e742006..65beebf106 100644
--- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPage.js
@@ -21,6 +21,7 @@ import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/Soft
import LandingPageView from './SoftwareProductLandingPageView.jsx';
import {actionTypes as modalActionTypes} from 'nfvo-components/modal/GlobalModalConstants.js';
import {onboardingMethod} from '../SoftwareProductConstants.js';
+import VNFImportActionHelper from '../vnfMarketPlace/VNFImportActionHelper.js';
export const mapStateToProps = ({softwareProduct, licenseModel: {licenseAgreement}}) => {
let {softwareProductEditor: {data:currentSoftwareProduct = {}}, softwareProductComponents, softwareProductCategories = []} = softwareProduct;
@@ -104,7 +105,11 @@ const mapActionsToProps = (dispatch, {version}) => {
OnboardingActionHelper.navigateToSoftwareProductComponentGeneralAndUpdateLeftPanel(dispatch, {softwareProductId, componentId, version });
},
/** for the next version */
- onAddComponent: () => SoftwareProductActionHelper.addComponent(dispatch)
+ onAddComponent: () => SoftwareProductActionHelper.addComponent(dispatch),
+
+ onBrowseVNF: (currentSoftwareProduct) => {
+ VNFImportActionHelper.open(dispatch, currentSoftwareProduct);
+ }
};
};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx
index 72a416473c..e18860c4f5 100644
--- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/landingPage/SoftwareProductLandingPageView.jsx
@@ -19,7 +19,9 @@ import Dropzone from 'react-dropzone';
import i18n from 'nfvo-utils/i18n/i18n.js';
+import Configuration from 'sdc-app/config/Configuration.js';
import DraggableUploadFileBox from 'nfvo-components/fileupload/DraggableUploadFileBox.jsx';
+import VnfRepositorySearchBox from 'nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx';
import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
import SoftwareProductComponentsList from '../components/SoftwareProductComponentsList.js';
@@ -104,19 +106,38 @@ class SoftwareProductLandingPageView extends React.Component {
}
renderProductDetails(isManual, isReadOnlyMode) {
- return (
- <div className='details-panel'>
- { !isManual && <div>
- <div className='software-product-landing-view-heading-title'>{i18n('Software Product Attachments')}</div>
- <DraggableUploadFileBox
- dataTestId='upload-btn'
- isReadOnlyMode={isReadOnlyMode}
- className={classnames('software-product-landing-view-top-block-col-upl', {'disabled': isReadOnlyMode})}
- onClick={() => this.refs.fileInput.open()}/>
- </div>
- }
- </div>
- );
+ let {onBrowseVNF, currentSoftwareProduct} = this.props;
+
+ if(Configuration.get('showBrowseVNF')) {
+ return (
+ <div className='details-panel'>
+ { !isManual && <div>
+ <div className='software-product-landing-view-heading-title'>{i18n('Software Product Attachments')}</div>
+ <VnfRepositorySearchBox
+ dataTestId='upload-btn'
+ isReadOnlyMode={isReadOnlyMode}
+ className={classnames('software-product-landing-view-top-block-col-upl showVnf', {'disabled': isReadOnlyMode})}
+ onClick={() => this.refs.fileInput.open()} onBrowseVNF={() => onBrowseVNF(currentSoftwareProduct)}/>
+ </div>
+ }
+ </div>
+ );
+ }
+ else {
+ return (
+ <div className='details-panel'>
+ { !isManual && <div>
+ <div className='software-product-landing-view-heading-title'>{i18n('Software Product Attachments')}</div>
+ <DraggableUploadFileBox
+ dataTestId='upload-btn'
+ isReadOnlyMode={isReadOnlyMode}
+ className={classnames('software-product-landing-view-top-block-col-upl', {'disabled': isReadOnlyMode})}
+ onClick={() => this.refs.fileInput.open()} onBrowseVNF={() => onBrowseVNF()}/>
+ </div>
+ }
+ </div>
+ );
+ }
}
handleImportSubmit(files, isReadOnlyMode, isManual) {
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js
new file mode 100644
index 0000000000..3fb0bfd637
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {connect} from 'react-redux';
+import VNFImportView from './VNFImportView.jsx';
+import VNFImportActionHelper from './VNFImportActionHelper.js';
+
+export const mapStateToProps = (response) => {
+ const { softwareProduct: { VNFMarketPlaceImport : { vnfItems } } } = response;
+ return {
+ vnfItems: vnfItems
+ };
+};
+
+export const mapActionsToProps = (dispatch) => {
+ return {
+ onCancel: () => VNFImportActionHelper.resetData(dispatch),
+ onSubmit: (csarId, selectedVendor) => {
+ VNFImportActionHelper.uploadData(selectedVendor, csarId, dispatch);
+ }
+ };
+};
+
+export default connect(mapStateToProps, mapActionsToProps, null, {withRef: true})(VNFImportView);
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js
new file mode 100644
index 0000000000..f8cec1c1cb
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import {actionTypes as modalActionTypes, modalSizes} from 'nfvo-components/modal/GlobalModalConstants.js';
+import {modalContentMapper} from 'sdc-app/common/modal/ModalContentMapper.js';
+import SoftwareProductActionHelper from 'sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js';
+import {actionTypes} from './VNFImportConstants.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+function baseUrl(selectedVendor) {
+ const restPrefix = Configuration.get('restPrefix');
+ let vspId = selectedVendor.id;
+ let version = selectedVendor.version;
+ return `${restPrefix}/v1.0/vendor-software-products/${vspId}/versions/${version.id}/vnfrepository`;
+}
+
+function getVNFMarketplace(dispatch, currentSoftwareProduct) {
+ return RestAPIUtil.fetch(`${baseUrl(currentSoftwareProduct)}/vnfpackages`, {
+ isAnonymous: false
+ })
+ .then((response) => {
+ dispatch({
+ type: actionTypes.OPEN,
+ response
+ });
+ dispatch({
+ type: modalActionTypes.GLOBAL_MODAL_SHOW,
+ data: {
+ modalComponentName: modalContentMapper.VNF_IMPORT,
+ title: i18n('Browse VNF'),
+ modalComponentProps: {
+ currentSoftwareProduct,
+ size: modalSizes.LARGE
+ }
+ }
+ });
+ })
+ .catch((error) => {
+ let errMessage = error.responseJSON ? error.responseJSON.message : i18n('VNF import failed msg');
+
+ dispatch({
+ type: modalActionTypes.GLOBAL_MODAL_ERROR,
+ data: {
+ title: i18n('VNF import failed title'),
+ msg: errMessage,
+ cancelButtonText: i18n('Ok')
+ }
+ });
+ });
+}
+
+function downloadCSARFile(csarId, currSoftwareProduct) {
+ let url = `${baseUrl(currSoftwareProduct)}/vnfpackage/${csarId}/download`;
+ return RestAPIUtil.fetch(url, {
+ dataType: 'binary',
+ isAnonymous: false
+ });
+}
+
+function getFileName(xhr, defaultFilename) {
+ let filename = '';
+ let contentDisposition = xhr.getResponseHeader('Content-Disposition') ? xhr.getResponseHeader('Content-Disposition') : '';
+ let match = contentDisposition.match(/filename=(.*?)(;|$)/);
+ if (match) {
+ filename = match[1].replace(/['"]/g, '');;
+ }
+ else {
+ filename = defaultFilename;
+ }
+ return filename;
+}
+
+function uploadVNFData(csarId, currSoftwareProduct, dispatch) {
+
+ let softwareProductId = currSoftwareProduct.id;
+ let version = currSoftwareProduct.version;
+
+ SoftwareProductActionHelper.uploadVNFFile(dispatch, {
+ csarId,
+ currSoftwareProduct,
+ failedNotificationTitle: i18n('Upload validation failed'),
+ softwareProductId,
+ version
+ });
+}
+
+function getTimestampString() {
+ let date = new Date();
+ let z = n => n < 10 ? '0' + n : n;
+ return `${date.getFullYear()}-${z(date.getMonth())}-${z(date.getDate())}_${z(date.getHours())}-${z(date.getMinutes())}`;
+}
+
+function showFileSaveDialog({blob, xhr, defaultFilename, addTimestamp}) {
+ let filename = getFileName(xhr, defaultFilename);
+
+ if (addTimestamp) {
+ filename = filename.replace(/(^.*?)\.([^.]+$)/, `$1_${getTimestampString()}.$2`);
+ }
+
+ let link = document.createElement('a');
+ let url = URL.createObjectURL(blob);
+ link.href = url;
+ link.download = filename;
+ link.style.display = 'none';
+ document.body.appendChild(link);
+ link.click();
+ setTimeout(function(){
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ }, 0);
+}
+
+const VNFImportActionHelper = {
+
+ open(dispatch, currentSoftwareProduct) {
+ getVNFMarketplace(dispatch, currentSoftwareProduct);
+ },
+
+ download(csarId, currSoftwareProduct) {
+ downloadCSARFile(csarId, currSoftwareProduct)
+ .then((blob, statusText, xhr) => showFileSaveDialog({blob, xhr, defaultFilename: 'MyNewCSAR.csar', addTimestamp: true}));
+ },
+
+ resetData(dispatch) {
+
+ dispatch({
+ type: modalActionTypes.GLOBAL_MODAL_CLOSE
+ });
+
+ dispatch({
+ type: actionTypes.RESET_DATA
+ });
+ },
+
+ getVNFMarketplace(dispatch) {
+ return getVNFMarketplace(dispatch);
+ },
+
+ uploadData(currSoftwareProduct, csarId, dispatch) {
+ this.resetData(dispatch);
+ uploadVNFData(csarId, currSoftwareProduct, dispatch);
+ }
+};
+
+export default VNFImportActionHelper;
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js
new file mode 100644
index 0000000000..763cf94ec7
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js
@@ -0,0 +1,22 @@
+
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ OPEN: null,
+ RESET_DATA: null
+});
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js
new file mode 100644
index 0000000000..4149a737c1
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {actionTypes} from './VNFImportConstants.js';
+
+
+export default (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.OPEN:
+ return {
+ ...state,
+ showModal: true,
+ vnfItems: action.response,
+ };
+ case actionTypes.RESET_DATA:
+ return {};
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx
new file mode 100644
index 0000000000..f56396f48d
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2017 Huawei Technologies Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import GridSection from 'nfvo-components/grid/GridSection.jsx';
+import GridItem from 'nfvo-components/grid/GridItem.jsx';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js';
+import Button from 'sdc-ui/lib/react/Button.js';
+import VNFImportActionHelper from '../vnfMarketPlace/VNFImportActionHelper.js';
+
+
+function VNFAction({action, isHeader, downloadCSAR, id, currSoftwareProduct}) {
+ if (isHeader) {
+ return <span>{action}</span>;
+ }
+ return (
+ <span>
+ <SVGIcon name='download' color='positive' onClick={() => {downloadCSAR(id, currSoftwareProduct);}}/>
+ </span>
+ );
+}
+
+function VNFSortableCellHeader({isHeader, data, isDes, onSort, activeSortColumn}) {
+ //TODO check icon sdc-ui
+ if (isHeader) {
+ if(activeSortColumn === data) {
+ return (
+ <span className='vnf-table-header' onClick={()=>{onSort(activeSortColumn);}}>
+ <span>{data}</span>
+ <span className={`header-sort-arrow ${isDes ? 'up' : 'down'}`}></span>
+ </span>
+ );
+ }
+ else {
+ return (
+ <span className='vnf-table-header' onClick={()=>{activeSortColumn = data; onSort(activeSortColumn);}}>
+ <span>{data}</span>
+ </span>
+ );
+ }
+ }
+ return (
+ <span className='vnf-table-cell'>
+ <span>{data}</span>
+ </span>
+ );
+}
+
+export function VNFItemList({vnf, isHeader, isDes, onSort, activeSortColumn, downloadCSAR, selectTableRow, selectedRow, currentSoftwareProduct}) {
+ let {csarId, name, version, provider, shortDesc, action} = vnf;
+ return (
+ <li className={`vnfBrowse-list-item ${isHeader ? 'header' : ''} ${csarId === selectedRow ? 'selectedRow' : ''}`}
+ data-test-id='vnfBrowse-list-item' onClick={() => {selectTableRow(csarId);}}>
+ <div className='table-cell vnftable-name' data-test-id='vnftable-name'>
+ <VNFSortableCellHeader isHeader={isHeader} data={name} isDes={isDes} onSort={(activeSort)=> {onSort('name', activeSort);}} activeSortColumn={activeSortColumn}/>
+ </div>
+ <div className='table-cell vnftable-version' data-test-id='vnftable-version'>
+ <VNFSortableCellHeader isHeader={isHeader} data={version} isDes={isDes} onSort={(activeSort)=> {onSort('version', activeSort);}} activeSortColumn={activeSortColumn}/>
+ </div>
+ <div className='table-cell vnftable-provider' data-test-id='vnftable-provider'>
+ <VNFSortableCellHeader isHeader={isHeader} data={provider} isDes={isDes} onSort={(activeSort)=> {onSort('provider', activeSort);}} activeSortColumn={activeSortColumn}/>
+ </div>
+ <div className='table-cell vnftable-shortDesc' data-test-id='vnftable-shortDesc'>
+ <VNFSortableCellHeader isHeader={isHeader} data={shortDesc} isDes={isDes} onSort={(activeSort)=> {onSort('shortDesc', activeSort);}} activeSortColumn={activeSortColumn}/>
+ </div>
+ <div className='table-cell vnftable-action' data-test-id='vnftable-action'>
+ <VNFAction isHeader={isHeader} action={action} downloadCSAR= {downloadCSAR} id={csarId} currSoftwareProduct={currentSoftwareProduct}/>
+ </div>
+ </li>
+ );
+}
+
+class VNFImportView extends React.Component {
+
+ state = {
+ localFilter: '',
+ sortDescending: true,
+ sortCrit: 'name',
+ activeSortColumn : 'Name',
+ selectedRow: ''
+ };
+
+ render() {
+ let {onCancel, onSubmit, currentSoftwareProduct} = this.props;
+
+ return (
+ <div className='vnf-creation-page'>
+ <GridSection className='vnf-grid-section'>
+ <GridItem colSpan='4'>
+ <ListEditorView
+ title={i18n('VNF List Title')}
+ filterValue={this.state.localFilter}
+ onFilter={filter => this.setState({localFilter: filter})}>
+ <VNFItemList
+ isHeader={true}
+ vnf={{csarId: 0, name: i18n('VNF Header Name'), version: i18n('VNF Header Version'),
+ provider: i18n('VNF Header Vendor'), shortDesc: i18n('VNF Header Desc'), action: i18n('VNF Header Action')}}
+ isDes={this.state.sortDescending}
+ onSort={(sortCriteria, activeSortCol) => this.setState({sortDescending: !this.state.sortDescending, sortCrit: sortCriteria, activeSortColumn: activeSortCol})}
+ activeSortColumn={this.state.activeSortColumn}/>
+ {this.sortVNFItems(this.filterVNFItems(),
+ this.state.sortDescending, this.state.sortCrit).map(vnf => <VNFItemList key={vnf.id} vnf={vnf}
+ downloadCSAR={this.downloadCSAR} selectTableRow={(selID) =>
+ {this.setState({selectedRow: selID});this.selectTableRow(selID);}} selectedRow={this.state.selectedRow} currentSoftwareProduct={currentSoftwareProduct}/>)}
+ </ListEditorView>
+ </GridItem>
+ <GridItem colSpan='4'>
+ <div className='vnf-modal'>
+ <Button className='vnf-submit' type='button' btnType='default' onClick={() =>
+ onSubmit(this.state.selectedRow, currentSoftwareProduct)}>{i18n('OK')}
+ </Button>
+ <Button className='Cancel' type='button' btnType='outline' onClick={onCancel}>{i18n('Cancel')}
+ </Button>
+ </div>
+ </GridItem>
+ </GridSection>
+ </div>
+ );
+ }
+
+ filterVNFItems() {
+ let {vnfItems} = this.props;
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return vnfItems.filter(
+ ({name = '', provider = '', version = '', shortDesc = ''}) =>
+ escape(name).match(filter) || escape(provider).match(filter) || escape(version).match(filter) || escape(shortDesc).match(filter));
+ }
+ else {
+ return vnfItems;
+ }
+ }
+
+ sortVNFItems(vnfItems, sortDesc, sortCrit) {
+ if (sortDesc) {
+ return vnfItems.sort((a, b) =>
+ {
+ if(a[sortCrit] < b[sortCrit]){
+ return -1;
+ }
+ else if(a[sortCrit] > b[sortCrit]){
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ });
+ }
+ else {
+ return vnfItems.reverse();
+ }
+ }
+
+ downloadCSAR(id, currSoftwareProduct) {
+ VNFImportActionHelper.download(id, currSoftwareProduct);
+ }
+
+}
+
+export default VNFImportView;