Integrate VNF Repository in Beijing release
Migrate the code
Change-Id: Ifccacf83634af32b034fd9c413e68f894f06d2f7
Issue-ID: VNFSDK-155
Signed-off-by: Murali-P <murali.p@huawei.com>
diff --git a/openecomp-ui/resources/scss/_components.scss b/openecomp-ui/resources/scss/_components.scss
index e18b260..7bd9010 100644
--- a/openecomp-ui/resources/scss/_components.scss
+++ b/openecomp-ui/resources/scss/_components.scss
@@ -22,6 +22,7 @@
@import "components/userNotifications";
@import "components/overlay";
@import "components/vspDetailsVendorSelect";
+@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 0000000..7e0085a
--- /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 99027d6..8d124c3 100644
--- a/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss
+++ b/openecomp-ui/resources/scss/modules/_softwareProductLandingPage.scss
@@ -160,12 +160,65 @@
color: $light-blue;
}
}
+ }
.software-product-landing-view-top-block-col-upl {
@extend .flex;
+ height: 215px;
+ text-align: center;
+ flex-direction: column;
+ justify-content: center;
+ border: 2px dashed $light-gray;
margin-bottom: 20px;
+ @extend .body-1;
+ align-items: center;
+ .upload-btn {
+ padding: 15px 55px;
}
+ .drag-text {
+ color: $blue;
+ @extend .body-1-semibold;
+ }
+ .or-text {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ color: $light-gray;
+ }
+ .upload {
+ width: 50%;
+ border : 0px !important;
+ }
+ .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/vnfMarketPlace/VnfRepositorySearchBox.jsx b/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx
new file mode 100644
index 0000000..ab8a18b
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/vnfMarketPlace/VnfRepositorySearchBox.jsx
@@ -0,0 +1,73 @@
+/*!
+ * 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'}`} />
+
+ <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 1a5817d..6be5db7 100644
--- a/openecomp-ui/src/nfvo-utils/RestAPIUtil.js
+++ b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js
@@ -41,6 +41,9 @@
function applySecurity(options, data) {
let headers = options.headers || (options.headers = {});
+ if (options.isAnonymous) {
+ return;
+ }
let authToken = localStorage.getItem(STORAGE_AUTH_KEY);
if (authToken) {
diff --git a/openecomp-ui/src/nfvo-utils/i18n/en.json b/openecomp-ui/src/nfvo-utils/i18n/en.json
index cbc2031..10ddb42 100644
--- a/openecomp-ui/src/nfvo-utils/i18n/en.json
+++ b/openecomp-ui/src/nfvo-utils/i18n/en.json
@@ -620,5 +620,14 @@
"VSPQuestionnaire/general/storageDataReplication/storageReplicationFrequency" : "Storage Replication Frequency",
"VSPQuestionnaire/general/storageDataReplication/storageReplicationDestination" : "Storage Replication Destination",
+ "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",
+
"GENERIC_ERROR": "An error has occurred. Please contact your System Administrator for further assistance."
}
diff --git a/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js b/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js
index 5b28c5d..745f01d 100644
--- a/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js
+++ b/openecomp-ui/src/sdc-app/common/modal/ModalContentMapper.js
@@ -24,6 +24,7 @@
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';
import PermissionsManager from 'sdc-app/onboarding/permissions/PermissionsManager.js';
import CommitCommentModal from 'nfvo-components/panel/versionController/components/CommitCommentModal.jsx';
import Tree from 'nfvo-components/tree/Tree.jsx';
@@ -48,7 +49,8 @@
VERSION_TREE: 'VERSION_TREE',
MERGE_EDITOR: 'MERGE_EDITOR',
REVISIONS_LIST: 'REVISIONS_LIST',
- VENDOR_SELECTOR: 'VENDOR_SELECTOR'
+ VENDOR_SELECTOR: 'VENDOR_SELECTOR',
+ VNF_IMPORT: 'VNF_IMPORT'
};
export const modalContentComponents = {
@@ -67,5 +69,6 @@
VERSION_TREE: Tree,
MERGE_EDITOR: MergeEditor,
REVISIONS_LIST: Revisions,
- VENDOR_SELECTOR: VendorSelector
+ VENDOR_SELECTOR: VendorSelector,
+ VNF_IMPORT: VNFImport
};
diff --git a/openecomp-ui/src/sdc-app/config/config.json b/openecomp-ui/src/sdc-app/config/config.json
index fbfaf1d..e9e0b55 100644
--- a/openecomp-ui/src/sdc-app/config/config.json
+++ b/openecomp-ui/src/sdc-app/config/config.json
@@ -7,5 +7,6 @@
"defaultRestCatalogPrefix": "/sdc1/feProxy/rest",
"defaultWebsocketPort" : "8181",
"defaultDebugWebsocketPort" : "9000",
- "defaultWebsocketPath" : "notification-api/ws/notificationHandler"
+ "defaultWebsocketPath" : "notification-api/ws/notificationHandler",
+ "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 25bd32e..877c786 100644
--- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductActionHelper.js
@@ -81,6 +81,12 @@
);
}
+function uploadVNFFile(csarId, softwareProductId, version) {
+ let verId = typeof version === 'object' ? version.id : version;
+ return RestAPIUtil.post(
+ `${baseUrl()}${softwareProductId}/versions/${verId}/vnfrepository/vnfpackage/${csarId}/import`
+ );
+}
function putSoftwareProduct({ softwareProduct, version }) {
return RestAPIUtil.put(
`${baseUrl()}${softwareProduct.id}/versions/${version.id}`,
@@ -421,6 +427,54 @@
});
},
+ 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 }
diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js
index f3de517..fd4f02c 100644
--- a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js
@@ -56,6 +56,8 @@
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,
@@ -150,5 +152,8 @@
}
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 34bfcee..f5f3b7e 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 { onboardingMethod } from '../SoftwareProductConstants.js';
import ScreensHelper from 'sdc-app/common/helpers/ScreensHelper.js';
import { enums, screenTypes } from 'sdc-app/onboarding/OnboardingConstants.js';
+import VNFImportActionHelper from '../vnfMarketPlace/VNFImportActionHelper.js';
export const mapStateToProps = ({
softwareProduct,
@@ -137,7 +138,12 @@
props: { softwareProductId, version, componentId }
}),
/** 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 bc8a2be..00f0c2a 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 'sdc-app/onboarding/softwareProduct/components/SoftwareProductComponents.js';
@@ -122,26 +124,55 @@
}
renderProductDetails(isManual, isReadOnlyMode) {
- return (
- <div className="details-panel">
- {!isManual && (
- <div>
- <div className="software-product-landing-view-heading-title">
- {i18n('Software Product Attachments')}
+ 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>
- <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>
- );
+ )}
+ </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 0000000..19efab6
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImport.js
@@ -0,0 +1,41 @@
+/*
+ * 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 0000000..3843330
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportActionHelper.js
@@ -0,0 +1,180 @@
+/*
+ * 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 && 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 = { id: 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.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 0000000..e4540dd
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportConstants.js
@@ -0,0 +1,21 @@
+/*
+ * 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 0000000..0f2638f
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportReducer.js
@@ -0,0 +1,31 @@
+/*
+ * 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 0000000..3a90c80
--- /dev/null
+++ b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/vnfMarketPlace/VNFImportView.jsx
@@ -0,0 +1,309 @@
+/*
+ * 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>
+ );
+ } 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;