diff options
Diffstat (limited to 'openecomp-ui')
17 files changed, 954 insertions, 24 deletions
diff --git a/openecomp-ui/resources/scss/_components.scss b/openecomp-ui/resources/scss/_components.scss index e18b2603e9..7bd90100e2 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 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 99027d66ed..8d124c3b40 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 0000000000..ab8a18b4c6 --- /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 1a5817de66..6be5db765c 100644 --- a/openecomp-ui/src/nfvo-utils/RestAPIUtil.js +++ b/openecomp-ui/src/nfvo-utils/RestAPIUtil.js @@ -41,6 +41,9 @@ const CONTENT_MD5_HEADER = 'Content-MD5'; 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 cbc2031b4b..10ddb4202e 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 5b28c5d7fa..745f01d0eb 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 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'; 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 @@ export const modalContentMapper = { 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 @@ export const modalContentComponents = { 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 fbfaf1de66..e9e0b55114 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 25bd32e468..877c7869bd 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 uploadFile(vspId, formData, version) { ); } +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 @@ 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 } diff --git a/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js b/openecomp-ui/src/sdc-app/onboarding/softwareProduct/SoftwareProductReducer.js index f3de517a1c..fd4f02cde1 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 { 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 @@ 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 34bfceec24..f5f3b7ebdb 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 { actionTypes as modalActionTypes } from 'nfvo-components/modal/GlobalMod 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 @@ const mapActionsToProps = (dispatch, { version }) => { 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 bc8a2be646..00f0c2a0cb 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 classnames from 'classnames'; 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 @@ 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')} + 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 0000000000..19efab68c8 --- /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 0000000000..3843330449 --- /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 0000000000..e4540dda3b --- /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 0000000000..0f2638f7ed --- /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 0000000000..3a90c8042f --- /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; |